Traits
Traits (특성)
D 언어에는 컴파일 타임에 컴파일러 내부의 정보를 끄집어내서 쓸 수 있게 해 주는 **특성(traits)**이라는 장치가 있어요. __traits(...)라는 특별한 문법으로 돌아가는 이 기능 덕분에, 코드가 컴파일되는 동안 타입이나 심볼, 함수의 성질을 직접 물어볼 수 있죠. 이번 장에서는 그 특성 문법과 각 특성이 뭘 하는지 하나씩 살펴볼게요.
본문
문법 (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
인자를 하나 받는데, 함수 심볼, 함수 리터럴, 델리게이트, 또는 함수 포인터 중 하나여야 해요. 함수의 반환값이 숨겨진 추가 파라미터로 전달된 포인터를 통해 스택에서 반환되면 true인 bool을 돌려줘요.
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): 이것은 다음과 같은 데에 쓰일 수 있어요:
- 레지스터로 값을 반환하는 게 흔히 더 빠르기 때문에, 자주 호출되는(hot) 함수가 가장 빠른 방법을 쓰는지 확인하는 검사로 사용할 수 있어요.
- 인라인 어셈블리로 함수를 올바르게 호출할 때 사용해요.
- 컴파일러가 이걸 올바르게 처리하는지 검증하는 일은 보통 난삽하고 어색한데, 이 특성은 효율적이고 직접적이며 간단한 테스트를 가능하게 해 줘요.
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)라면, 등가 비교가 이뤄져요. 비교가 올바르게 계산되려면 두 람다 함수에 대해 다음 조건들이 충족되어야 해요:
- 람다 함수 인자의 명시적 인자 타입이 템플릿 인스턴스화여선 안 돼요. 다른 어떤 인자 타입(기본, 사용자 정의, 템플릿)은 지원돼요.
- 람다 함수 본문은 단일 표현식(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)
- D 언어 공식 사양 — Traits 챕터 전문: https://dlang.org/spec/traits.html
- 관련 챕터: Conditional Compilation (조건부 컴파일), Error Handling (오류 처리)