템플릿 믹스인

템플릿 믹스인 (Template Mixins)

템플릿 믹스인(Template Mixin)은 템플릿 선언의 본문에 담긴 임의의 선언 묶음을 그대로 가져와서, 현재 스코프에 삽입하는 D 언어의 기능이에요. 일반적인 템플릿 인스턴스화와 달리 선언이 정의된 곳이 아니라 믹스인이 놓인 곳에서 본문이 평가되기 때문에, 매개변수화된 '보일러플레이트' 코드를 주입하거나 템플릿화된 중첩 함수를 만들 때 특히 유용해요.

출처: https://dlang.org/spec/template-mixin.html

본문

템플릿 믹스인(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

템플릿 믹스인은 모듈, 클래스, 구조체, 공용체(union)의 선언 목록 어디에도 올 수 있고, 하나의 문장(statement)으로도 쓸 수 있어요. 이때 MixinTemplateName은 반드시 TemplateDeclaration이나 TemplateMixinDeclaration을 가리켜야 해요. 그리고 템플릿 선언이 매개변수를 요구하지 않는다면 TemplateArguments는 생략할 수 있어요.

일반적인 템플릿 인스턴스화(template instantiation)와 달리, 템플릿 믹스인의 본문은 템플릿 선언이 정의된 곳이 아니라 믹스인이 등장한 스코프 안에서 평가돼요. 마치 템플릿 본문을 복사해서 믹스인이 있는 자리(중첩 스코프)에 붙여넣은 것과 비슷하죠. 이 덕분에 매개변수화된 '보일러플레이트' 코드를 주입하기도 좋고, 템플릿 인스턴스화로는 항상 만들기 어려운 템플릿화된 중첩 함수를 만들 때도 유용해요.

TemplateMixinDeclarationTemplateDeclaration과 같지만, 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)

믹스인의 선언들은 중첩 스코프에 놓인 다음, 바깥 스코프로 '수입(import)'돼요. 만약 믹스인 안의 선언 이름이 바깥 스코프의 선언과 같다면, 바깥 쪽 선언이 믹스인의 선언을 덮어써요.

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를 붙여두면, 그 이름으로 충돌하는 심볼들을 구분할 수 있어요.

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 function)를 추가할 수 있어요.

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)

집합 타입(aggregate type)에는 추가 소멸자(destructor)를 믹스인할 수 있어요. 소멸자들은 선언된 순서와 반대 순서로 실행돼요.

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`
}

더 알아보기 (Learn more)