템플릿 믹스인

템플릿 믹스인 (Template Mixins)

TemplateMixin은 TemplateDeclaration 본문의 임의 선언 집합을 가져와서 현재 컨텍스트(context)에 삽입해요. 일반 템플릿 인스턴스화와 달리 믹스인의 본문은 템플릿이 정의된 곳이 아니라 믹스인이 나타난 스코프에서 평가돼요. 이 문서는 믹스인 문법, 매개변수화, 스코프 해석, 그리고 애그리게이트 타입 믹스인을 다루어요.

출처: Template Mixins

본문

TemplateMixin은 TemplateDeclaration 본문의 임의 선언 집합을 가져와서 현재 컨텍스트에 삽입해요.

TemplateMixinDeclaration:
    mixin template Identifier TemplateParameters Constraintopt { DeclDefsopt }

TemplateMixin:
    mixin MixinTemplateName TemplateArgumentsopt Identifieropt ;
    mixin Identifier = MixinTemplateName TemplateArgumentsopt ;

MixinTemplateName:
    . MixinQualifiedIdentifier
    MixinQualifiedIdentifier
    Typeof . MixinQualifiedIdentifier

MixinQualifiedIdentifier:
    Identifier
    Identifier . MixinQualifiedIdentifier
    TemplateInstance . MixinQualifiedIdentifier

TemplateMixin은 모듈, 클래스, struct, union의 선언 목록에 나타나거나 문(statement)으로 나타날 수 있어요. MixinTemplateName은 TemplateDeclaration 또는 TemplateMixinDeclaration을 가리켜야 해요. TemplateDeclaration이 매개변수를 요구하지 않으면 TemplateArguments는 생략할 수 있어요.

템플릿 인스턴스화(instantiation)와 달리, 템플릿 믹스인의 본문은 템플릿이 정의된 곳이 아니라 믹스인이 나타난 스코프에서 평가돼요. 이는 템플릿 본문을 믹스인의 위치로 잘라 붙이는 것(cut and paste)과 유사하며, 중첩 스코프처럼 동작해요. 매개변수화된 '보일러플레이트' 코드를 주입하는 데 유용하고, 템플릿 인스턴스화로는 항상 가능하지 않은 템플릿화된 중첩 함수를 만드는 데도 유용해요.

TemplateMixinDeclaration은 TemplateDeclaration과 같지만, TemplateMixin 밖에서는 인스턴스화할 수 없어요.

int y = 3;

mixin template Foo()
{
    int abc() { return y; }
}

void test()
{
    int y = 8;
    mixin Foo; // local y is picked up, not global y
    assert(abc() == 8);
}
import std.stdio : writeln;

mixin template Foo()
{
    int x = 5;
}

mixin Foo;

struct Bar
{
    mixin Foo;
}

void main()
{
    writeln("x = ", x);         // prints 5
    {
        Bar b;
        int x = 3;

        writeln("b.x = ", b.x); // prints 5
        writeln("x = ", x);     // prints 3
        {
            mixin Foo;
            writeln("x = ", x); // prints 5
            x = 4;
            writeln("x = ", x); // prints 4
        }
        writeln("x = ", x);     // prints 3
    }
    writeln("x = ", x);         // prints 5
}

믹스인 매개변수 (Mixin Parameters)

믹스인은 매개변수화될 수 있어요:

mixin template Foo(T)
{
    T x = 5;
}

mixin Foo!(int);           // create x of type int

믹스인은 alias 매개변수를 사용해 심볼을 매개변수화할 수 있어요:

mixin template Foo(alias b)
{
    int abc() { return b; }
}

void test()
{
    int y = 8;
    mixin Foo!(y);
    assert(abc() == 8);
}

예제 (Example)

이 예제는 임의의 문(이 경우 임의의 문은 굵게 표시됨)에 대한 제네릭 Duff's device를 구현하기 위해 믹스인을 사용해요. 중첩 함수와 delegate 리터럴이 생성되는데, 이들은 컴파일러가 인라인할 수 있어요:

import std.stdio : writeln;

mixin template duffs_device(alias low, alias high, alias fun)
{
    void duff_loop()
    {
        if (low < high)
        {
            auto n = (high - low + 7) / 8;
            switch ((high - low) % 8)
            {
                case 0: do { fun(); goto case;
                case 7:      fun(); goto case;
                case 6:      fun(); goto case;
                case 5:      fun(); goto case;
                case 4:      fun(); goto case;
                case 3:      fun(); goto case;
                case 2:      fun(); goto case;
                case 1:      fun(); continue;
                default:     assert(0, "Impossible");
                        } while (--n > 0);
            }
        }
    }
}

void main()
{
    int i = 1;
    int j = 11;

    mixin duffs_device!(i, j, delegate { writeln("foo"); });
    duff_loop();  // executes foo() 10 times
}

믹스인 스코프 (Mixin Scope)

믹스인의 선언들은 중첩 스코프에 배치된 다음 주변 스코프로 '가져와져'(imported)요. 믹스인의 선언 이름이 주변 스코프의 선언 이름과 같으면 주변 선언이 믹스인 것을 덮어써요(override):

import std.stdio : writeln;

int x = 3;

mixin template Foo()
{
    int x = 5;
    int y = 5;
}

mixin Foo;
int y = 3;

void main()
{
    writeln("x = ", x);  // prints 3
    writeln("y = ", y);  // prints 3
}

믹스인은 선언이 바깥 스코프에 의해 덮어써져도 자신만의 스코프를 가져요:

import std.stdio : writeln;

int x = 4;

mixin template Foo()
{
    int x = 5;
    int bar() { return x; }
}

mixin Foo;

void main()
{
    writeln("x = ", x);         // prints 4
    writeln("bar() = ", bar()); // prints 5
}

같은 스코프에 서로 다른 두 믹스인이 놓이고 각각 같은 이름의 선언을 정의하면, 그 선언이 참조될 때 모호성(ambiguity) 오류가 발생해요:

import std.stdio : writeln;

mixin template Foo()
{
    int x = 5;
    void func(int x) { }
}

mixin template Bar()
{
    int x = 4;
    void func(long x) { }
}

mixin Foo;
mixin Bar;

void main()
{
    writeln("x = ", x);    // error, x is ambiguous
    func(1);               // error, func is ambiguous
}

func() 호출이 모호한 이유는 Foo.funcBar.func가 서로 다른 스코프에 있기 때문이에요.

모호성 해석 (Resolving Ambiguities)

믹스인에 Identifier가 있으면 충돌하는 심볼들을 구분(disambiguate)하는 데 사용할 수 있어요:

import std.stdio : writeln;

int x = 6;

mixin template Foo()
{
    int x = 5;
    int y = 7;
    void func() { }
}

mixin template Bar()
{
    int x = 4;
    void func() { }
}

mixin Foo F;
mixin Bar B;

void main()
{
    writeln("y = ", y);     // prints 7
    writeln("x = ", x);     // prints 6
    writeln("F.x = ", F.x); // prints 5
    writeln("B.x = ", B.x); // prints 4
    F.func();                  // calls Foo.func
    B.func();                  // calls Bar.func
}

alias 선언을 사용해 서로 다른 믹스인에서 선언된 함수들의 오버로드 집합(overload set)을 만들 수 있어요:

mixin template Foo()
{
    void func(int x) {  }
}

mixin template Bar()
{
    void func(long x) {  }
}

mixin Foo!() F;
mixin Bar!() B;

alias func = F.func;
alias func = B.func;

void main()
{
    func(1);  // calls B.func
    func(1L); // calls F.func
}

애그리게이트 타입 믹스인 (Aggregate Type Mixins)

믹스인 가상 함수 (Mixin Virtual Functions)

믹스인은 클래스에 가상 함수(virtual functions)를 추가할 수 있어요:

import std.stdio : writeln;

mixin template Foo()
{
    void func() { writeln("Foo.func()"); }
}

class Bar
{
    mixin Foo;
}

class Code : Bar
{
    override void func() { writeln("Code.func()"); }
}

void main()
{
    Bar b = new Bar();
    b.func();      // calls Foo.func()

    b = new Code();
    b.func();      // calls Code.func()
}

믹스인 소멸자 (Mixin Destructors)

애그리게이트 타입은 추가 소멸자(destructors)를 믹스인할 수 있어요. 소멸자들은 선언 순서의 반대 순서로 실행돼요:

import std.stdio;

mixin template addNewDtor()
{
    ~this()
    {
        writeln("Mixin dtor");
    }
}

struct S
{
    ~this()
    {
        writeln("Struct dtor");
    }

    mixin addNewDtor;
}

void main()
{
    S s;
    // prints `Mixin dtor`
    // prints `Struct dtor`
}

더 알아보기