Traits

Traits (특성)

D 언어에는 컴파일 타임에 컴파일러 내부의 정보를 끄집어내서 쓸 수 있게 해 주는 **특성(traits)**이라는 장치가 있어요. __traits(...)라는 특별한 문법으로 돌아가는 이 기능 덕분에, 코드가 컴파일되는 동안 타입이나 심볼, 함수의 성질을 직접 물어볼 수 있죠. 이번 장에서는 그 특성 문법과 각 특성이 뭘 하는지 하나씩 살펴볼게요.

출처: https://dlang.org/spec/traits.html

본문

문법 (Grammar)

특성은 언어에 대한 확장으로, 프로그램이 컴파일 타임에 컴파일러 내부의 정보에 접근할 수 있게 해 줍니다. 이것은 컴파일 타임 리플렉션(compile time reflection)이라고도 불러요. 마치 프래그마(Pragmas)처럼, 쉽게 확장할 수 있는 특별한 문법으로 되어 있어서 필요할 때마다 새로운 기능을 추가할 수 있도록 설계되었어요.

TraitsExpression:
    __traits ( TraitsKeyword , TraitsArguments )

TraitsKeyword:
    isAbstractClass
    isArithmetic
    isOverlapped
    isAssociativeArray
    isFinalClass
    isPOD
    isNested
    isFuture
    isDeprecated
    isFloating
    isIntegral
    isScalar
    isStaticArray
    isUnsigned
    isDisabled
    isVirtualFunction
    isVirtualMethod
    isAbstractFunction
    isFinalFunction
    isStaticFunction
    isOverrideFunction
    isTemplate
    isRef
    isOut
    isLazy
    isReturnOnStack
    isCopyable
    isZeroInit
    isModule
    isPackage
    isCOMClass
    hasMember
    hasCopyConstructor
    hasMoveConstructor
    hasPostblit
    needsDestruction
    identifier
    fullyQualifiedName
    getAliasThis
    getAttributes
    isBitfield
    getBitfieldOffset
    getBitfieldWidth
    getFunctionAttributes
    getFunctionVariadicStyle
    getLinkage
    getLocation
    getMember
    getOverloads
    getParameterStorageClasses
    getPointerBitmap
    getCppNamespaces
    getVisibility
    getProtection
    getTargetInfo
    getVirtualFunctions
    getVirtualMethods
    getUnitTests
    parent
    child
    classInstanceSize
    classInstanceAlignment
    getVirtualIndex
    allMembers
    derivedMembers
    isSame
    compiles
    toType
    initSymbol
    parameters

TraitsArguments:
    TraitsArgument
    TraitsArgument , TraitsArguments

TraitsArgument:
    AssignExpression
    Type

타입 특성 (Type Traits)

isArithmetic

인자들이 전부 **산술 타입(ArithmeticType)**이거나 그런 타입으로 타입이 정해진 표현식이라면 true를 돌려줘요. 그렇지 않으면 false를 돌려주고요, 인자가 하나도 없을 때도 false를 돌려줍니다. 산술 타입이란 정수 타입과 부동 소수점 타입을 말해요.

import std.stdio;

void main()
{
    int i;
    static assert(__traits(isArithmetic, int));
    static assert(__traits(isArithmetic, i, i+1, int));
    static assert(!__traits(isArithmetic));
    static assert(!__traits(isArithmetic, int*));
}

isOverlapped

인자를 하나 받는데, 반드시 애그리거트 타입의 필드여야 해요. 그 필드의 메모리가 다른 필드와 겹쳐 있다면(즉 필드가 익명이든 이름이 붙은 union의 멤버라면) true를, 아니면 false를 돌려줘요. 필드가 아닌 인자(타입, 리터럴, 애그리거트 타입 자체)에 대해서는 false를 돌려줍니다.

struct S
{
    int a;
    union
    {
        int x;
        float y;
    }
}

static assert(__traits(isOverlapped, S.x));
static assert(__traits(isOverlapped, S.y));
static assert(!__traits(isOverlapped, S.a));

union U
{
    int x;
    float y;
}

static assert(__traits(isOverlapped, U.x));
static assert(__traits(isOverlapped, U.y));

isFloating

인자들이 전부 부동 소수점 타입이거나 그렇게 타입이 정해진 표현식이라면 true를, 아니면 false를 돌려줘요. 인자가 없으면 false입니다. 부동 소수점 타입은 float, double, real, ifloat, idouble, ireal, cfloat, cdouble, creal, 부동 소수점 타입의 벡터, 그리고 부동 소수점 기본 타입을 가진 enum이에요.

import core.simd : float4;

enum E : float { a, b }

static assert(__traits(isFloating, float));
static assert(__traits(isFloating, E));
static assert(__traits(isFloating, float4));

static assert(!__traits(isFloating, float[4]));

isIntegral

인자들이 전부 정수 타입이거나 그렇게 타입이 정해진 표현식이라면 true를, 아니면 false를 돌려줘요. 인자가 없으면 false입니다. 정수 타입은 byte, ubyte, short, ushort, int, uint, long, ulong, cent, ucent, bool, char, wchar, dchar, 정수 타입의 벡터, 그리고 정수 기본 타입을 가진 enum이에요.

import core.simd : int4;

enum E { a, b }

static assert(__traits(isIntegral, bool));
static assert(__traits(isIntegral, char));
static assert(__traits(isIntegral, int));
static assert(__traits(isIntegral, E));
static assert(__traits(isIntegral, int4));

static assert(!__traits(isIntegral, float));
static assert(!__traits(isIntegral, int[4]));
static assert(!__traits(isIntegral, void*));

isScalar

인자들이 전부 스칼라 타입이거나 그렇게 타입이 정해진 표현식이라면 true를, 아니면 false를 돌려줘요. 인자가 없으면 false입니다. 스칼라 타입은 정수 타입, 부동 소수점 타입, 포인터 타입, 스칼라 타입의 벡터, 그리고 스칼라 기본 타입을 가진 enum이에요.

import core.simd : int4, void16;

enum E { a, b }

static assert(__traits(isScalar, bool));
static assert(__traits(isScalar, char));
static assert(__traits(isScalar, int));
static assert(__traits(isScalar, float));
static assert(__traits(isScalar, E));
static assert(__traits(isScalar, int4));
static assert(__traits(isScalar, void*)); // Includes pointers!

static assert(!__traits(isScalar, int[4]));
static assert(!__traits(isScalar, void16));
static assert(!__traits(isScalar, void));
static assert(!__traits(isScalar, typeof(null)));
static assert(!__traits(isScalar, Object));

isUnsigned

인자들이 전부 부호 없는 타입이거나 그렇게 타입이 정해진 표현식이라면 true를, 아니면 false를 돌려줘요. 인자가 없으면 false입니다. 부호 없는 타입은 ubyte, ushort, uint, ulong, ucent, bool, char, wchar, dchar, 부호 없는 타입의 벡터, 그리고 부호 없는 기본 타입을 가진 enum이에요.

import core.simd : uint4;

enum SignedEnum { a, b }
enum UnsignedEnum : uint { a, b }

static assert(__traits(isUnsigned, bool));
static assert(__traits(isUnsigned, char));
static assert(__traits(isUnsigned, uint));
static assert(__traits(isUnsigned, UnsignedEnum));
static assert(__traits(isUnsigned, uint4));

static assert(!__traits(isUnsigned, int));
static assert(!__traits(isUnsigned, float));
static assert(!__traits(isUnsigned, SignedEnum));
static assert(!__traits(isUnsigned, uint[4]));
static assert(!__traits(isUnsigned, void*));

isStaticArray

isArithmetic처럼 동작하는데, 대상이 **정적 배열 타입(static array types)**이라는 점만 달라요.

import core.simd : int4;

enum E : int[4] { a = [1, 2, 3, 4] }

static array = [1, 2, 3]; // Not a static array: the type is inferred as int[] not int[3].

static assert(__traits(isStaticArray, void[0]));
static assert(__traits(isStaticArray, E));
static assert(!__traits(isStaticArray, int4));
static assert(!__traits(isStaticArray, array));

isAssociativeArray

isArithmetic처럼 동작하는데, 대상이 **연관 배열 타입(associative array types)**이라는 점만 달라요.

isAbstractClass

인자들이 전부 추상 클래스 타입이거나 그렇게 타입이 정해진 표현식이라면 true를, 아니면 false를 돌려줘요. 인자가 없으면 false입니다.

import std.stdio;

abstract class C { int foo(); }

void main()
{
    C c;
    writeln(__traits(isAbstractClass, C));
    writeln(__traits(isAbstractClass, c, C));
    writeln(__traits(isAbstractClass));
    writeln(__traits(isAbstractClass, int*));
}

출력 결과는 다음과 같아요:

true
true
false
false

isFinalClass

isAbstractClass처럼 동작하는데, 대상이 final 클래스라는 점만 달라요.

isCOMClass

인자를 하나 받아요. 그 인자가 COM 클래스인 클래스 선언을 가리키는 심볼이라면 true를, 아니면 false를 돌려줍니다.

isCopyable

인자를 하나 받아요. 그 인자가 복사 가능한 타입이면 true를, 아니면 false를 돌려줘요.

struct S
{
}
static assert( __traits(isCopyable, S));

struct T
{
    @disable this(this); // disable copy construction
}
static assert(!__traits(isCopyable, T));

isPOD

인자를 하나 받는데, 반드시 타입이어야 해요. 그 타입이 POD 타입이면 true를, 아니면 false를 돌려줘요.

toType

인자를 하나 받는데, 반드시 string 타입의 표현식으로 평가되어야 해요. 그 문자열의 내용은 구현이 이미 본 타입의 맹글된(mangled) 내용과 일치해야 합니다. 오직 D 맹글링만 지원돼요. C++ 맹글링 같은 다른 맹글링은 지원하지 않아요. 돌려주는 값은 타입이에요.

template Type(T) { alias Type = T; }

Type!(__traits(toType, "i")) j = 3; // j is declared as type `int`

static assert(is(Type!(__traits(toType, (int*).mangleof)) == int*));

__traits(toType, "i") x = 4; // x is also declared as type `int`

이유(Rationale): .mangleof 프로퍼티의 역연산을 제공하기 위해서예요.

isZeroInit

인자를 하나 받는데 반드시 타입이어야 해요. 그 타입의 기본 초기화 값이 전부 0 비트라면 true를, 아니면 false를 돌려줘요.

struct S1 { int x; }
struct S2 { int x = -1; }

static assert(__traits(isZeroInit, S1));
static assert(!__traits(isZeroInit, S2));

void test()
{
    int x = 3;
    static assert(__traits(isZeroInit, typeof(x)));
}

// `isZeroInit` will always return true for a class C
// because `C.init` is null reference.

class C { int x = -1; }

static assert(__traits(isZeroInit, C));

// For initializing arrays of element type `void`.
static assert(__traits(isZeroInit, void));

hasCopyConstructor

인자는 타입이에요. 그 타입이 복사 생성자(copy constructor)를 가진 struct라면 true를, 아니면 false를 돌려줘요. 복사 생성자는 이동 생성자(move constructor)나 postblit과는 구별돼요.


import std.stdio;

struct S { }

class C { }

struct P
{
    this(ref P rhs) {} // copy constructor
}

struct B
{
    this(this) {} // postblit
}

void main()
{
    writeln(__traits(hasCopyConstructor, S)); // false
    writeln(__traits(hasCopyConstructor, C)); // false
    writeln(__traits(hasCopyConstructor, P)); // true
    writeln(__traits(hasCopyConstructor, B)); // false, this is a postblit
}

hasMoveConstructor

인자는 타입이에요. 그 타입이 이동 생성자(move constructor)를 가진 struct라면 true를, 아니면 false를 돌려줘요. 이동 생성자는 복사 생성자나 postblit과는 구별돼요.


import std.stdio;

struct S
{
    this(S rhs) {} // move constructor
}

class C { }

struct P
{
    this(ref P rhs) {} // copy constructor
}

struct B
{
    this(this) {} // postblit
}

void main()
{
    writeln(__traits(hasMoveConstructor, S)); // true
    writeln(__traits(hasMoveConstructor, C)); // false
    writeln(__traits(hasMoveConstructor, P)); // false
    writeln(__traits(hasMoveConstructor, B)); // false, this is a postblit
}

hasPostblit

인자는 타입이에요. 그 타입이 postblit을 가진 struct라면 true를, 아니면 false를 돌려줘요. 참고로 postblit은 복사 생성자와는 구별돼요.


import std.stdio;

struct S
{
}

class C
{
}

struct P
{
    this(ref P rhs) {}
}

struct B
{
    this(this) {}
}

void main()
{
    writeln(__traits(hasPostblit, S)); // false
    writeln(__traits(hasPostblit, C)); // false
    writeln(__traits(hasPostblit, P)); // false, this is a copy ctor
    writeln(__traits(hasPostblit, B)); // true
}

needsDestruction

T가 정교한 파괴(elaborate destruction)가 필요한 값 타입이면 true가 결과예요. 여기에는 명시적 ~this() 소멸자를 가지거나(소멸이 필요한 필드 때문에) 컴파일러가 만들어 낸 소멸자를 가지는 struct, 그런 struct의 정적 배열, 그리고 그런 기본 타입을 가진 enum이 포함돼요.

class C { ~this(); }
struct S { ~this(); }

static assert(!__traits(needsDestruction, C));
static assert(__traits(needsDestruction, S));
static assert(!__traits(needsDestruction, S[0]));
static assert(__traits(needsDestruction, S[1]));

getAliasThis

인자를 하나 받는데, 타입이에요. 그 타입에 alias this 선언이 있으면 그 선언에 쓰인 멤버들의 이름(string들)의 ValueSeq을 돌려줘요. 없으면 빈 시퀀스를 돌려줍니다.

alias AliasSeq(T...) = T;

struct S1
{
    string var;
    alias var this;
}
static assert(__traits(getAliasThis, S1) == AliasSeq!("var"));
static assert(__traits(getAliasThis, int).length == 0);

pragma(msg, __traits(getAliasThis, S1));
pragma(msg, __traits(getAliasThis, int));

출력 결과는 다음과 같아요:

AliasSeq!("var")
AliasSeq!()

getPointerBitmap

인자는 타입이에요. 결과는 그 타입의 인스턴스가 사용하는 메모리를 기술하는 size_t 배열이에요. 배열의 첫 원소는 타입의 크기(클래스라면 classInstanceSize)이고요. 다음 원소들은 타입 인스턴스가 차지하는 메모리 안에서 GC가 관리하는 포인터들의 위치를 기술해요. 타입 T에 대해, 배열 값의 비트로 표현되는 가능한 포인터 개수는 T.sizeof / size_t.sizeof개예요. 이 배열은 정밀 GC(precise GC)가 가짜 포인터(false pointers)를 피하는 데 사용할 수 있어요.

void main()
{
    static class C
    {
        // implicit virtual function table pointer not marked
        // implicit monitor field not marked, usually managed manually
        C next;
        size_t sz;
        void* p;
        void function () fn; // not a GC managed pointer
    }

    static struct S
    {
        size_t val1;
        void* p;
        C c;
        byte[] arr;          // { length, ptr }
        void delegate () dg; // { context, func }
    }

    static assert (__traits(getPointerBitmap, C) == [6*size_t.sizeof, 0b010100]);
    static assert (__traits(getPointerBitmap, S) == [7*size_t.sizeof, 0b0110110]);
}

getVirtualFunctions

getVirtualMethods와 같은데, 아무것도 오버라이드하지 않는 final 함수까지 포함한다는 점이 달라요.

getVirtualMethods

첫 번째 인자는 클래스 타입 또는 클래스 타입의 표현식이에요. 두 번째 인자는 그 클래스의 함수 중 하나의 이름과 매칭되는 문자열이고요. 결과는 그 함수의 가상 오버로드들의 심볼 시퀀스예요. 아무것도 오버라이드하지 않는 final 함수는 포함하지 않아요.

import std.stdio;

class D
{
    this() { }
    ~this() { }
    void foo() { }
    int foo(int) { return 2; }
}

void main()
{
    D d = new D();

    foreach (t; __traits(getVirtualMethods, D, "foo"))
        writeln(typeid(typeof(t)));

    alias b = typeof(__traits(getVirtualMethods, D, "foo"));
    foreach (t; b)
        writeln(typeid(t));

    auto i = __traits(getVirtualMethods, d, "foo")[1](1);
    writeln(i);
}

출력 결과는 다음과 같아요:

void()
int()
void()
int()
2

classInstanceSize

인자를 하나 받는데, 반드시 클래스 타입이거나 클래스 타입의 표현식으로 평가되어야 해요. 결과는 size_t 타입이고, 값은 그 클래스 타입의 런타임 인스턴스 바이트 수예요. 클래스의 정적 타입에 기반하며, 다형적(polymorphic) 타입에 기반하지 않아요.

classInstanceAlignment

인자를 하나 받는데, 반드시 클래스 타입이거나 클래스 타입의 표현식으로 평가되어야 해요. 결과는 size_t 타입이고, 값은 그 클래스 타입의 런타임 인스턴스 정렬(alignment)이에요. 클래스의 정적 타입에 기반하며, 다형적 타입에 기반하지 않아요.

initSymbol

인자를 하나 받는데, 반드시 class, struct 또는 union 타입으로 평가되어야 해요. 주어진 타입의 어떤 인스턴스의 초기 상태를 담고 있는 const(void)[]를 돌려줘요. 이 슬라이스는 임의의 타입 T에 대해 다음과 같이 만들어져요:

  • ptr는 T의 초기화 심볼을 가리키거나, T가 0으로 초기화되는 struct/union이면 null을 가리켜요.
  • length는 인스턴스 하나의 크기와 같아요. 즉 struct/union이면 T.sizeof, 클래스면 __traits(classInstanceSize, T)예요.

이 특성은 TypeInfo.initializer()의 동작과 일치하지만, TypeInfo를 쓸 수 없는 상황에서도 사용할 수 있어요. 이 특성은 CTFE 중에는 사용할 수 없는데, 초기화 심볼의 실제 주소는 링커가 정하기 때문에 컴파일 타임에 그 주소가 없기 때문이에요.

import core.stdc.stdlib;

class C
{
    int i = 4;
}

/// Initializes a malloc'ed instance of `C`
void main()
{
    const void[] initSym = __traits(initSymbol, C);

    void* ptr = malloc(initSym.length);
    scope (exit) free(ptr);

    // Note: allocated memory will only be written to through `c`, so cast is safe
    ptr[0..initSym.length] = cast(void[]) initSym[];

    C c = cast(C) ptr;
    assert(c.i == 4);
}

함수 특성 (Function Traits)

getVirtualIndex

인자를 하나 받는데 반드시 함수로 평가되어야 해요. 결과는 ptrdiff_t로, 그 함수가 부모 타입의 vtable 안에서 차지하는 인덱스가 들어 있어요. 넘겨진 함수가 final이고 가상 함수를 오버라이드하지 않는다면 대신 -1을 돌려줘요.

isVirtualFunction

isVirtualMethod와 같은데, 아무것도 오버라이드하지 않는 final 함수에 대해서도 true를 돌려준다는 점이 달라요.

isVirtualMethod

인자를 하나 받아요. 그 인자가 가상 함수라면 true를, 아니면 false를 돌려줘요. 아무것도 오버라이드하지 않는 final 함수는 false를 돌려줘요.

import std.stdio;

struct S
{
    void bar() { }
}

class C
{
    void bar() { }
}

void main()
{
    writeln(__traits(isVirtualMethod, C.bar));  // true
    writeln(__traits(isVirtualMethod, S.bar));  // false
}

isAbstractFunction

인자를 하나 받아요. 그 인자가 추상 함수라면 true를, 아니면 false를 돌려줘요.

import std.stdio;

struct S
{
    void bar() { }
}

class C
{
    void bar() { }
}

class AC
{
    abstract void foo();
}

void main()
{
    writeln(__traits(isAbstractFunction, C.bar));   // false
    writeln(__traits(isAbstractFunction, S.bar));   // false
    writeln(__traits(isAbstractFunction, AC.foo));  // true
}

isFinalFunction

인자를 하나 받아요. 그 인자가 final 함수라면 true를, 아니면 false를 돌려줘요.

import std.stdio;

struct S
{
    void bar() { }
}

class C
{
    void bar() { }
    final void foo();
}

final class FC
{
    void foo();
}

void main()
{
    writeln(__traits(isFinalFunction, C.bar));  // false
    writeln(__traits(isFinalFunction, S.bar));  // false
    writeln(__traits(isFinalFunction, C.foo));  // true
    writeln(__traits(isFinalFunction, FC.foo)); // true
}

isOverrideFunction

인자를 하나 받아요. 그 인자가 override로 표시된 함수라면 true를, 아니면 false를 돌려줘요.

import std.stdio;

class Base
{
    void foo() { }
}

class Foo : Base
{
    override void foo() { }
    void bar() { }
}

void main()
{
    writeln(__traits(isOverrideFunction, Base.foo)); // false
    writeln(__traits(isOverrideFunction, Foo.foo));  // true
    writeln(__traits(isOverrideFunction, Foo.bar));  // false
}

isStaticFunction

인자를 하나 받아요. 그 인자가 static 함수, 즉 컨텍스트 포인터가 없는 함수라면 true를, 아니면 false를 돌려줘요.

struct A
{
    int foo() { return 3; }
    static int boo(int a) { return a; }
}

void main()
{
    assert(__traits(isStaticFunction, A.boo));
    assert(!__traits(isStaticFunction, A.foo));
    assert(__traits(isStaticFunction, main));
}

isReturnOnStack

인자를 하나 받는데, 함수 심볼, 함수 리터럴, 델리게이트, 또는 함수 포인터 중 하나여야 해요. 함수의 반환값이 숨겨진 추가 파라미터로 전달된 포인터를 통해 스택에서 반환되면 truebool을 돌려줘요.

struct S { int[20] a; }
int test1();
S test2();

static assert(__traits(isReturnOnStack, test1) == false);
static assert(__traits(isReturnOnStack, test2) == true);

구현 정의 (Implementation Defined): 이 값은 사용 중인 함수 ABI 호출 규약에 의해 정해지는데, 흔히 복잡합니다. 모범 사례 (Best Practices): 이것은 다음과 같은 데에 쓰일 수 있어요:

  1. 레지스터로 값을 반환하는 게 흔히 더 빠르기 때문에, 자주 호출되는(hot) 함수가 가장 빠른 방법을 쓰는지 확인하는 검사로 사용할 수 있어요.
  2. 인라인 어셈블리로 함수를 올바르게 호출할 때 사용해요.
  3. 컴파일러가 이걸 올바르게 처리하는지 검증하는 일은 보통 난삽하고 어색한데, 이 특성은 효율적이고 직접적이며 간단한 테스트를 가능하게 해 줘요.

getFunctionVariadicStyle

인자를 하나 받는데, 함수 심볼이거나 함수·델리게이트·함수 포인터 타입 중 하나여야 해요. 지원되는 가변 인자(variadic) 종류를 나타내는 문자열을 돌려줘요.

result kind access example
"none" not a variadic function void foo();
"argptr" D style variadic function _argptr and _arguments void bar(...)
"stdarg" C style variadic function core.stdc.stdarg extern (C) void abc(int, ...)
"typesafe" typesafe variadic function array on stack void def(int[] ...)
import core.stdc.stdarg;

void novar() {}
extern(C) void cstyle(int, ...) {}
extern(C++) void cppstyle(int, ...) {}
void dstyle(...) {}
void typesafe(int[]...) {}

static assert(__traits(getFunctionVariadicStyle, novar) == "none");
static assert(__traits(getFunctionVariadicStyle, cstyle) == "stdarg");
static assert(__traits(getFunctionVariadicStyle, cppstyle) == "stdarg");
static assert(__traits(getFunctionVariadicStyle, dstyle) == "argptr");
static assert(__traits(getFunctionVariadicStyle, typesafe) == "typesafe");

static assert(__traits(getFunctionVariadicStyle, (int[] a...) {}) == "typesafe");
static assert(__traits(getFunctionVariadicStyle, typeof(cstyle)) == "stdarg");

getFunctionAttributes

인자를 하나 받는데, 함수 심볼, 함수 리터럴, 또는 함수 포인터 중 하나여야 해요. 그 함수의 모든 속성 중 사용자 정의 속성을 제외한 것들의 string ValueSeq을 돌려줘요. 사용자 정의 속성(UDAs, user-defined attributes)은 getAttributes 특성으로 얻을 수 있어요. 속성이 하나도 없으면 빈 시퀀스를 돌려주고요. 참고: 돌려받는 시퀀스에서 속성의 순서는 구현에 따라 정해지므로 믿고 쓰면 안 돼요. 현재 지원되는 속성 목록은 다음과 같아요:

  • pure, nothrow, @nogc, @property, @system, @trusted, @safe, ref 그리고 @live

참고: ref는 반환 타입에 적용되기는 하지만 함수 속성이에요. 추가로 다음 속성들은 비정적 멤버 함수에서만 유효해요:

  • const, immutable, inout, shared

예를 들어:

int sum(int x, int y) pure nothrow { return x + y; }

pragma(msg, __traits(getFunctionAttributes, sum));

struct S
{
    void test() const @system { }
}

pragma(msg, __traits(getFunctionAttributes, S.test));

출력 결과는 다음과 같아요:

AliasSeq!("pure", "nothrow", "@system")
AliasSeq!("const", "@system")

일부 속성은 추론될 수 있다는 점도 참고하세요. 예를 들어:

pragma(msg, __traits(getFunctionAttributes, (int x) @trusted { return x * 2; }));

출력 결과는 다음과 같아요:

AliasSeq!("pure", "nothrow", "@nogc", "@trusted")

변수 특성 (Variable Traits)

isRef

인자를 하나 받아요. 그 인자가 ref 저장 클래스를 가진 변수/파라미터 선언이라면 true를, 아니면 false를 돌려줘요.

void foo(ref int x, int y)
{
    static assert(__traits(isRef, x));
    static assert(!__traits(isRef, y));

    int i;
    ref j = i;
    static assert(!__traits(isRef, i));
    static assert(__traits(isRef, j));
}

ref int get();
// get returns by reference, but it's not a ref itself
static assert(!__traits(isRef, get));

함수 파라미터 특성 (Function Parameter Traits)

isOut, isLazy

인자를 하나 받아요. 그 인자가 out 또는 lazy 선언이라면 true를, 아니면 false를 돌려줘요.

void fooref(ref int x)
{
    static assert(__traits(isRef, x));
    static assert(!__traits(isOut, x));
    static assert(!__traits(isLazy, x));
}

void fooout(out int x)
{
    static assert(!__traits(isRef, x));
    static assert(__traits(isOut, x));
    static assert(!__traits(isLazy, x));
}

void foolazy(lazy int x)
{
    static assert(!__traits(isRef, x));
    static assert(!__traits(isOut, x));
    static assert(__traits(isLazy, x));
}

getParameterStorageClasses

인자를 두 개 받아요. 첫 번째는 함수 심볼, 함수 호출, 또는 함수·델리게이트·함수 포인터 타입 중 하나여야 해요. 두 번째는 어느 파라미터인지 가리키는 정수인데, 첫 번째 파라미터가 0이에요. 그 파라미터의 저장 클래스들을 나타내는 문자열 ValueSeq을 돌려줘요.

ref int foo(return ref const int* p, scope int* a, out int b, lazy int c);

static assert(__traits(getParameterStorageClasses, foo, 0)[0] == "return");
static assert(__traits(getParameterStorageClasses, foo, 0)[1] == "ref");

static assert(__traits(getParameterStorageClasses, foo, 1)[0] == "scope");
static assert(__traits(getParameterStorageClasses, foo, 2)[0] == "out");
static assert(__traits(getParameterStorageClasses, typeof(&foo), 3)[0] == "lazy");

int* p, a;
int b, c;

static assert(__traits(getParameterStorageClasses, foo(p, a, b, c), 1)[0] == "scope");
static assert(__traits(getParameterStorageClasses, foo(p, a, b, c), 2)[0] == "out");
static assert(__traits(getParameterStorageClasses, foo(p, a, b, c), 3)[0] == "lazy");

parameters

함수 안에서만 쓸 수 있어요. 인자를 받지 않고, 바깥쪽 함수의 파라미터들로 이루어진 lvalue 시퀀스를 돌려줘요.

alias AliasSeq(A...) = A;

void f(int n, char c)
{
    alias PS = __traits(parameters);
    PS[0]++; // increment n
    static assert(is(typeof(PS) == AliasSeq!(int, char)));

    // output parameter names
    static foreach (i, p; PS)
    {
        pragma(msg, __traits(identifier, p));
    }
}

int add(int x, int y)
{
    return x + y;
}

int forwardToAdd(int x, int y)
{
    return add(__traits(parameters));
    // equivalent to;
    //return add(x, y);
}

함수가 중첩(nested)되어 있으면, 돌려받는 파라미터들은 바깥 함수가 아니라 안쪽 함수의 파라미터들이에요.

int nestedExample(int x)
{
    // outer function's parameters
    static assert(typeof(__traits(parameters)).length == 1);

    int add(int x, int y)
    {
        // inner function's parameters
        static assert(typeof(__traits(parameters)).length == 2);
        return x + y;
    }

    return add(x, x);
}

class C
{
    int opApply(int delegate(size_t, C) dg)
    {
        if (dg(0, this)) return 1;
        return 0;
    }
}

void foreachExample(C c, int x)
{
    foreach(idx; 0..5)
    {
        static assert(is(typeof(__traits(parameters)) == AliasSeq!(C, int)));
    }
    foreach(idx, elem; c)
    {
        //  __traits(parameters) sees past the delegate passed to opApply
        static assert(is(typeof(__traits(parameters)) == AliasSeq!(C, int)));
    }
}

심볼 특성 (Symbol Traits)

fullyQualifiedName

타입이나 심볼의 완전한 이름(fully qualified name)을 얻어요. 인자를 하나 받는데, 타입·표현식·심볼 중 하나일 수 있고, 문자열 하나를 돌려줘요. 인자가 심볼이 아닌 표현식 e라면, 결과는 typeof(e)를 인자로 준 것과 동일해요.

module plugh;

static assert(__traits(fullyQualifiedName, int) == "int"); // type
static assert(__traits(fullyQualifiedName, new Object) == "object.Object"); // expression

int i;
static assert(__traits(fullyQualifiedName, i) == "plugh.i"); // symbol

struct MyStruct {}
static assert(__traits(fullyQualifiedName, const MyStruct[]) == "const(plugh.MyStruct[])"); // type

isNested

인자를 하나 받아요. 그 인자가 내부에 컨텍스트 포인터를 저장하는 중첩(nested) 타입이면 true를, 아니면 false를 돌려줘요. 중첩 타입은 클래스, struct, 함수가 될 수 있어요.

isFuture

인자를 하나 받아요. 그 인자가 @__future 속성으로 표시된 심볼이면 true를, 아니면 false를 돌려줘요. 현재 @__future 키워드를 지원하는 것은 함수와 변수 선언뿐이에요.

isDeprecated

인자를 하나 받아요. 그 인자가 deprecated 키워드로 표시된 심볼이면 true를, 아니면 false를 돌려줘요.

deprecated("No longer supported")
int i;

struct A
{
    int foo() { return 1; }

    deprecated("please use foo")
    int bar() { return 1; }
}

static assert(__traits(isDeprecated, i));
static assert(!__traits(isDeprecated, A.foo));
static assert(__traits(isDeprecated, A.bar));

isDisabled

인자를 하나 받고, 그 인자가 @disable로 표시된 함수·변수·매니페스트 상수·enum 멤버 선언이면 true를 돌려줘요.

struct Foo
{
    @disable int i;
    @disable void foo();
    void bar(){}
}

static assert(__traits(isDisabled, Foo.i));
static assert(__traits(isDisabled, Foo.foo));
static assert(!__traits(isDisabled, Foo.bar));

다른 어떤 선언이라도, 설령 @disable이 문법적으로 유효한 속성이라 하더라도, 그 표기는 아무 효과가 없기 때문에 false를 돌려줘요.

@disable struct Bar{}

static assert(!__traits(isDisabled, Bar));

isTemplate

인자를 하나 받아요. 그 인자나 그 인자의 오버로드 중 하나라도 템플릿이면 true를, 아니면 false를 돌려줘요.

void foo(T)(){}
static assert(__traits(isTemplate, foo));
static assert(!__traits(isTemplate, foo!int()));
static assert(!__traits(isTemplate, "string"));

isModule

인자를 하나 받아요. 그 인자가 모듈을 가리키는 심볼이면 true를, 아니면 false를 돌려줘요. 패키지 모듈은 모듈로 직접 임포트되지 않았더라도 모듈로 간주돼요.

import core.thread;
import std.algorithm.sorting;

// A regular package (no package.d)
static assert(!__traits(isModule, core));
// A package module (has a package.d file)
// Note that we haven't imported std.algorithm directly.
// (In other words, we don't have an "import std.algorithm;" directive.)
static assert(__traits(isModule, std.algorithm));
// A regular module
static assert(__traits(isModule, std.algorithm.sorting));

isPackage

인자를 하나 받아요. 그 인자가 패키지를 가리키는 심볼이면 true를, 아니면 false를 돌려줘요.

import std.algorithm.sorting;
static assert(__traits(isPackage, std));
static assert(__traits(isPackage, std.algorithm));
static assert(!__traits(isPackage, std.algorithm.sorting));

hasMember

첫 번째 인자는 멤버를 가진 타입, 또는 멤버를 가진 타입의 표현식이에요. 두 번째 인자는 문자열이고요. 그 문자열이 타입의 유효한 프로퍼티라면 true를, 아니면 false를 돌려줘요.

import std.stdio;

struct S
{
    int m;
}

void main()
{
    S s;

    static assert(__traits(hasMember, S, "m"));
    static assert(__traits(hasMember, s, "m"));
    static assert(!__traits(hasMember, S, "y"));
    static assert(!__traits(hasMember, S, "write")); // false, but callable like a member via UFCS
    static assert(__traits(hasMember, int, "sizeof"));
    static assert(__traits(hasMember, 5, "sizeof"));
}

identifier

인자를 하나 받는데, 심볼이에요. 그 심볼의 식별자를 문자열 리터럴로 돌려줘요.

int var = 123;
static assert(__traits(identifier, var) == "var");

getAttributes

인자를 하나 받는데, 심볼이에요. 붙어 있는 모든 사용자 정의 속성(user-defined attributes)의 시퀀스를 돌려줘요. UDA가 없으면 빈 시퀀스를 돌려줍니다.

@(3) int a;
@("string", 7) int b;

enum Foo;
@Foo int c;

pragma(msg, __traits(getAttributes, a));
pragma(msg, __traits(getAttributes, b));
pragma(msg, __traits(getAttributes, c));

출력 결과는 다음과 같아요:

AliasSeq!(3)
AliasSeq!("string", 7)
AliasSeq!((Foo))

isBitfield

인자를 하나 받는데, struct나 클래스 안의 필드로 해석되는 한정 심볼이에요. 그 심볼이 비트필드(bitfield)라면 true가 결과예요.

struct S
{
    int a;
    int b:2, c:6;
}

static assert(!__traits(isBitfield, S.a));
static assert(__traits(isBitfield, S.b));
static assert(__traits(isBitfield, S.c));

getBitfieldOffset

인자를 하나 받는데, struct나 클래스 안의 필드로 해석되는 한정 심볼이에요. 결과는 uint예요. 필드가 비트필드라면 필드에서 최하위 비트(least significant bit)의 비트 번호를 돌려줘요. 가장 오른쪽 비트가 오프셋 0이고, 가장 왼쪽 비트는 (32비트 int 필드의 경우) 오프셋 31이에요. 필드가 비트필드가 아니면 0을 돌려줘요.

struct S
{
    int a, b;
    int :2, c:3;
}

static assert(__traits(getBitfieldOffset, S.b) == 0);
static assert(__traits(getBitfieldOffset, S.c) == 2);

getBitfieldWidth

인자를 하나 받는데, struct나 클래스 안의 필드로 해석되는 한정 심볼이에요. 결과는 uint예요. 필드가 비트필드라면 그 비트 수(너비)를 돌려줘요. 필드가 비트필드가 아니면 그 타입의 비트 수를 돌려줘요.

struct S
{
    int a, b;
    int :2, c:3;
}

static assert(__traits(getBitfieldWidth, S.b) == 32);
static assert(__traits(getBitfieldWidth, S.c) == 3);

getLinkage

인자를 하나 받는데, 선언 심볼이거나 함수·델리게이트·함수 포인터·struct·클래스·인터페이스의 타입이에요. 그 선언의 LinkageAttribute를 나타내는 문자열을 돌려줘요. 문자열은 다음 중 하나예요:

  • "D"
  • "C"
  • "C++"
  • "Windows"
  • "Objective-C"
  • "System"
extern (C) int fooc();
alias aliasc = fooc;

static assert(__traits(getLinkage, fooc) == "C");
static assert(__traits(getLinkage, aliasc) == "C");

extern (C++) struct FooCPPStruct {}
extern (C++) class FooCPPClass {}
extern (C++) interface FooCPPInterface {}

static assert(__traits(getLinkage, FooCPPStruct) == "C++");
static assert(__traits(getLinkage, FooCPPClass) == "C++");
static assert(__traits(getLinkage, FooCPPInterface) == "C++");

getLocation

인자를 하나 받는데 심볼이에요. 오버로드들을 구분하려면 원하는 인덱스의 getOverloads 결과를 getLocation에 넘기세요. 문자열 하나와 int 두 개의 ValueSeq을 돌려주는데, 각각 그 인자가 선언된 파일 이름, 줄 번호, 열 번호에 대응해요.

getMember

인자를 두 개 받는데, 두 번째는 반드시 문자열이에요. 결과는 첫 번째 인자 뒤에 .(마침표)가 이어지고, 그다음 두 번째 인자가 식별자로 붙은 형태의 표현식이에요.

import std.stdio;

struct S
{
    int mx;
    static int my;
}

void main()
{
    S s;

    __traits(getMember, s, "mx") = 1;  // same as s.mx=1;
    writeln(__traits(getMember, s, "m" ~ "x")); // 1

    // __traits(getMember, S, "mx") = 1;  // error, no this for S.mx
    __traits(getMember, S, "my") = 2;  // ok
}

getOverloads

  • 첫 번째 인자는 애그리거트 타입이나 인스턴스, 또는 모듈이에요.
  • 두 번째 인자는 돌려받을 멤버(들)의 이름과 매칭되는 string이에요.
  • 세 번째 인자는 bool이고 선택 사항이에요. true면 결과에 템플릿 오버로드도 포함돼요.
  • 결과는 주어진 이름의 모든 오버로드로 이루어진 심볼 시퀀스예요.
import std.stdio;

class D
{
    void foo() { }
    int foo(int) { return 2; }
    void bar(T)() { return T.init; }
    class bar(int n) {}
}

void main()
{
    D d = new D();

    alias fooOverloads = __traits(getOverloads, D, "foo");
    foreach (o; fooOverloads)
        writeln(typeid(typeof(o)));

    // typeof on a symbol sequence gives a type sequence
    foreach (T; typeof(fooOverloads))
        writeln(typeid(T));

    // calls d.foo(3)
    auto i = __traits(getOverloads, d, "foo")[1](3);
    assert(i == 2);

    // pass true to include templates
    // calls std.stdio.writeln(i)
    __traits(getOverloads, std.stdio, "writeln", true)[0](i);

    foreach (o; __traits(getOverloads, D, "bar", true))
        writeln(o.stringof);
}

출력 결과는 다음과 같아요:

void function()
int function(int)
void function()
int function(int)
2
bar(T)()
bar(int n)

getCppNamespaces

인자는 심볼이에요. 결과는 그 심볼이 속한 네임스페이스들에 대응하는 문자열들의 ValueSeq인데, 비어 있을 수도 있어요.

extern(C++, "ns") struct Foo {}
struct Bar {}
extern(C++, __traits(getCppNamespaces, Foo)) struct Baz {}

static assert(__traits(getCppNamespaces, Foo) ==  __traits(getCppNamespaces, Baz));

void main()
{
    static assert(__traits(getCppNamespaces, Foo)[0] == "ns");
    static assert(!__traits(getCppNamespaces, Bar).length);
    static assert(__traits(getCppNamespaces, Foo) ==  __traits(getCppNamespaces, Baz));
}

getVisibility

인자는 심볼이에요. 결과는 그 심볼의 가시성 수준(visibility level)을 주는 문자열로, "public", "private", "protected", "export", 또는 "package" 중 하나예요.

import std.stdio;

class D
{
    export void foo() { }
    public int bar;
}

void main()
{
    D d = new D();

    auto i = __traits(getVisibility, d.foo);
    writeln(i);

    auto j = __traits(getVisibility, d.bar);
    writeln(j);
}

출력 결과는 다음과 같아요:

export
public

getProtection

getVisibility의 하위 호환용 별칭(alias)이에요.

getTargetInfo

문자열 키를 인자로 받아요. 결과는 요청된 타깃 정보를 기술하는 표현식이에요.

version (CppRuntime_Microsoft)
    static assert(__traits(getTargetInfo, "cppRuntimeLibrary") == "libcmt");

키는 구현에 따라 정의되어서 이국적인 타깃에 대한 관련 데이터를 허용해요. 항상 사용 가능한 신뢰할 수 있는 하위 집합이 있어요:

  • "cppRuntimeLibrary" - 이 툴체인의 C++ 런타임 라이브러리 선호도
  • "cppStd" - extern(C++) 코드가 지원하는 C++ 표준 버전으로, C++ 컴파일러의 __cplusplus 매크로와 동일해요
  • "floatAbi" - 부동 소수점 ABI; "hard", "soft", 또는 "softfp"일 수 있어요
  • "objectFormat" - 타깃 객체 파일 형식

getUnitTests

인자를 하나 받는데, 애그리거트(예: struct/class/module)의 심볼이에요. 그 애그리거트의 모든 단위 테스트 함수들로 이루어진 심볼 시퀀스가 결과예요. 돌려받는 함수들은 보통의 중첩 static 함수처럼 동작해서, CTFE도 동작하고 UDA에도 접근할 수 있어요. 참고: -unittest 플래그를 컴파일러에 넘겨야 해요. 플래그를 넘기지 않으면 __traits(getUnitTests)는 항상 빈 시퀀스를 돌려줘요.

module foo;

import core.runtime;
import std.stdio;

struct name { string name; }

class Foo
{
    unittest
    {
        writeln("foo.Foo.unittest");
    }
}

@name("foo") unittest
{
    writeln("foo.unittest");
}

template Tuple (T...)
{
    alias Tuple = T;
}

shared static this()
{
  // Override the default unit test runner to do nothing. After that, "main" will
  // be called.
  Runtime.moduleUnitTester = { return true; };
}

void main()
{
    writeln("start main");

    alias tests = Tuple!(__traits(getUnitTests, foo));
    static assert(tests.length == 1);

    alias attributes = Tuple!(__traits(getAttributes, tests[0]));
    static assert(attributes.length == 1);

    foreach (test; tests)
        test();

    foreach (test; __traits(getUnitTests, Foo))
        test();
}

기본적으로 위 코드는 다음과 같이 출력해요:

start main
foo.unittest
foo.Foo.unittest

parent

인자를 하나 받는데 반드시 심볼로 평가되어야 해요. 그 심볼의 부모가 되는 심볼이 결과예요.

child

인자를 두 개 받아요. 첫 번째는 심볼이나 표현식이어야 해요. 두 번째는 첫 번째 인자의 멤버에 대한 alias 같은 심볼이고요. 결과는 두 번째 인자를, 그 this 컨텍스트가 첫 번째 인자의 값으로 설정된 채 해석한 것이에요.

import std.stdio;

struct A
{
    int i;
    int foo(int j) {
        return i * j;
    }
    T bar(T)(T t) {
        return i + t;
    }
}

alias Ai = A.i;
alias Abar = A.bar!int;

void main()
{
    A a;

    __traits(child, a, Ai) = 3;
    writeln(a.i);
    writeln(__traits(child, a, A.foo)(2));
    writeln(__traits(child, a, Abar)(5));
}

출력 결과는 다음과 같아요:

3
6
8

allMembers

인자를 하나 받는데, 모듈·struct·union·클래스·인터페이스·enum·템플릿 인스턴스화 중 하나로 평가되어야 해요. 문자열 리터럴들의 시퀀스를 돌려주는데, 각각 그 인자의 멤버 이름이며(인자가 클래스면 기본 클래스들의 모든 멤버도 합침) 이름은 중복되지 않아요. 내장 프로퍼티는 포함하지 않아요.

import std.stdio;

class D
{
    this() { }
    ~this() { }
    void foo() { }
    int foo(int) { return 0; }
}

void main()
{
    auto b = [ __traits(allMembers, D) ];
    writeln(b);
    // ["__ctor", "__dtor", "foo", "toString", "toHash", "opCmp", "opEquals",
    // "Monitor", "factory"]
}

결과에서 문자열이 나타나는 순서는 정의되어 있지 않아요.

derivedMembers

인자를 하나 받는데, 타입 또는 타입의 표현식으로 평가되어야 해요. 문자열 리터럴들의 시퀀스를 돌려주는데, 각각 그 타입의 어떤 멤버의 이름이며 이름은 중복되지 않아요. 기본 클래스의 멤버 이름은 포함하지 않아요. 내장 프로퍼티도 포함하지 않아요.

import std.stdio;

class D
{
    this() { }
    ~this() { }
    void foo() { }
    int foo(int) { return 0; }
}

void main()
{
    auto a = [__traits(derivedMembers, D)];
    writeln(a);    // ["__ctor", "__dtor", "foo"]
}

결과에서 문자열이 나타나는 순서는 정의되어 있지 않아요.

isSame

두 인자를 비교해서 bool로 평가돼요. (alias가 해석된 뒤) 두 인자가 같은 심볼이면 결과는 true예요.

struct S { }

int foo();
int bar();

static assert(__traits(isSame, foo, foo));
static assert(!__traits(isSame, foo, bar));
static assert(!__traits(isSame, foo, S));
static assert(__traits(isSame, S, S));
static assert(!__traits(isSame, object, S));
static assert(__traits(isSame, object, object));

alias daz = foo;
static assert(__traits(isSame, foo, daz));

isSame은 인스턴스화되지 않은 템플릿에도 매칭돼요.

struct Foo(T){
    T x;
}

struct Bar(T){
    T x;
}

struct Point(T){
    T x;
    T y;
}

enum isFooOrBar(alias FB) = __traits(isSame, FB, Foo) || __traits(isSame, FB, Bar);

static assert(isFooOrBar!(Foo));
static assert(isFooOrBar!(Bar));
static assert(!isFooOrBar!(Point));

두 인자가 리터럴이나 enum으로 이루어진 표현식으로서 같은 값으로 평가되면 결과는 true예요.

enum e = 3;
static assert(__traits(isSame, (e), 3));
static assert(__traits(isSame, 5, 2 + e));

두 인자가 둘 다 람다 함수(또는 람다 함수에 대한 alias)라면, 등가 비교가 이뤄져요. 비교가 올바르게 계산되려면 두 람다 함수에 대해 다음 조건들이 충족되어야 해요:

  1. 람다 함수 인자의 명시적 인자 타입이 템플릿 인스턴스화여선 안 돼요. 다른 어떤 인자 타입(기본, 사용자 정의, 템플릿)은 지원돼요.
  2. 람다 함수 본문은 단일 표현식(return 문 없이)이어야 하는데, 그 표현식은 오직 숫자 값, 매니페스트 상수, enum 값, 함수 인자, 함수 호출만 담아야 해요. 표현식이 지역 변수나 return 문을 담고 있으면 그 함수는 비교 불가(incomparable)로 간주돼요.

이 제약들이 충족되지 않으면 함수는 비교 불가로 간주되고 결과는 false예요.

static assert(__traits(isSame, (a, b) => a + b, (c, d) => c + d));
static assert(__traits(isSame, a => ++a, b => ++b));
static assert(!__traits(isSame, (int a, int b) => a + b, (a, b) => a + b));
static assert(__traits(isSame, (a, b) => a + b + 10, (c, d) => c + d + 10));

int f() { return 2; }

void test(alias pred)()
{
    // f() from main is a different function from top-level f()
    static assert(!__traits(isSame, (int a) => a + f(), pred));
}

void main()
{
    // lambdas accessing local variables are considered incomparable
    int b;
    static assert(!__traits(isSame, a => a + b, a => a + b));

    // lambdas calling other functions are comparable
    int f() { return 3;}
    static assert(__traits(isSame, a => a + f(), a => a + f()));
    test!((int a) => a + f())();
}

class A
{
    int a;
    this(int a)
    {
        this.a = a;
    }
}

class B
{
    int a;
    this(int a)
    {
        this.a = a;
    }
}

static assert(__traits(isSame, (A a) => ++a.a, (A b) => ++b.a));
// lambdas with different data types are considered incomparable,
// even if the memory layout is the same
static assert(!__traits(isSame, (A a) => ++a.a, (B a) => ++a.a));

두 인자가 튜플들이라면, 전개(expansion) 후 두 튜플의 길이가 같고 각 n번째 인자의 쌍이 앞서 명시한 제약들을 지킬 때 결과는 true예요.

import std.meta;

struct S { }

// like __traits(isSame,0,0) && __traits(isSame,1,1)
static assert(__traits(isSame, AliasSeq!(0,1), AliasSeq!(0,1)));
// like __traits(isSame,S,std.meta) && __traits(isSame,1,1)
static assert(!__traits(isSame, AliasSeq!(S,1), AliasSeq!(std.meta,1)));
// the length of the sequences is different
static assert(!__traits(isSame, AliasSeq!(1), AliasSeq!(1,2)));

compiles

모든 인자가 컴파일되면(즉 의미적으로 올바르면) true인 bool을 돌려줘요. 인자는 문법적으로 올바른 심볼, 타입, 또는 표현식일 수 있어요. 인자는 문(statement)이나 선언(declaration)이 될 수 없고, 대신 함수 리터럴 표현식으로 감쌀 수 있어요. 인자가 없으면 결과는 false예요.

static assert(!__traits(compiles));
static assert(__traits(compiles, 1 + 1)); // expression
static assert(__traits(compiles, typeof(1))); // type
static assert(__traits(compiles, object)); // symbol
static assert(__traits(compiles, 1, 2, 3, int, long));
static assert(!__traits(compiles, 3[1])); // semantic error
static assert(!__traits(compiles, 1, 2, 3, int, long, 3[1]));

enum n = 3;
// wrap a declaration/statement in a function literal
static assert(__traits(compiles, { int[n] arr; }));
static assert(!__traits(compiles, { foreach (e; n) {} }));

struct S
{
    static int s1;
    int s2;
}

static assert(__traits(compiles, S.s1 = 0));
static assert(!__traits(compiles, S.s2 = 0));
static assert(!__traits(compiles, S.s3));

int foo();

static assert(__traits(compiles, foo));
static assert(__traits(compiles, foo + 1)); // call foo with optional parens
static assert(!__traits(compiles, &foo + 1));

이것은 다음과 같은 데에 유용해요:

  • 제네릭 코드 안에서 컴파일러가 내놓는 따라가기 힘든 오류 메시지보다 더 나은 오류 메시지(static assert 사용)를 주는 데 있어요.
  • 템플릿 부분 특수화(partial specialization)가 허용하는 것보다 더 세밀한 특수화를 하는 데 있어요.

더 알아보기 (Learn more)