타입

타입 (Types)

D는 정적으로 타입이 지정되는(statically typed) 언어예요. 모든 표현식은 타입을 가지며, 타입은 표현식이 담을 수 있는 값을 제약하고 그 값에 대한 연산의 의미를 결정해요. 이 문서는 D의 기본 데이터 타입, 파생 데이터 타입, 사용자 정의 타입, 그리고 다양한 타입 변환 규칙을 다루어요.

출처: Types

본문

D는 정적으로 타입이 지정돼요. 모든 표현식은 타입을 가져요. 타입은 표현식이 담을 수 있는 값을 제약하고, 그 값에 대한 연산의 의미(semantics)를 결정해요.

Type:
    TypeCtorsopt BasicType TypeSuffixesopt

TypeCtors:
    TypeCtor
    TypeCtor TypeCtors

TypeCtor:
    const
    immutable
    inout
    shared

BasicType:
    FundamentalType
    . QualifiedIdentifier
    QualifiedIdentifier
    Typeof
    Typeof . QualifiedIdentifier
    TypeCtor ( Type )
    Vector
    TraitsExpression
    MixinType

Vector:
    __vector ( VectorBaseType )

VectorBaseType:
    Type

FundamentalType:
    void
    ArithmeticType

ArithmeticType:
    bool
    byte
    ubyte
    short
    ushort
    int
    uint
    long
    ulong
    cent
    ucent
    char
    wchar
    dchar
    float
    double
    real
    ifloat
    idouble
    ireal
    cfloat
    cdouble
    creal

TypeSuffixes:
    TypeSuffix TypeSuffixesopt

TypeSuffix:
    *
    [ ]
    [ AssignExpression ]
    [ AssignExpression .. AssignExpression ]
    [ Type ]
    delegate Parameters MemberFunctionAttributesopt
    function Parameters FunctionAttributesopt

QualifiedIdentifier:
    Identifier
    Identifier . QualifiedIdentifier
    TemplateInstance
    TemplateInstance . QualifiedIdentifier
    Identifier [ AssignExpression ]
    Identifier [ AssignExpression ] . QualifiedIdentifier
  • Basic Data Types(기본 데이터 타입)은 잎 타입(leaf types)이에요.
  • Derived Data Types(파생 데이터 타입)은 잎 타입 위에 구축돼요.
  • User-Defined Types(사용자 정의 타입)은 기본 및 파생 타입의 애그리게이트예요.

기본 데이터 타입 (Basic Data Types)

기본 데이터 타입 키워드 기본 초기화 값 (.init) 설명
void 기본 초기화 값 없음 void는 값을 갖지 않아요
bool false 불리언 값
byte 0 부호 있는 8비트
ubyte 0u 부호 없는 8비트
short 0 부호 있는 16비트
ushort 0u 부호 없는 16비트
int 0 부호 있는 32비트
uint 0u 부호 없는 32비트
long 0L 부호 있는 64비트
ulong 0uL 부호 없는 64비트
cent 0 부호 있는 128비트
ucent 0u 부호 없는 128비트
float float.nan 32비트 부동소수점
double double.nan 64비트 부동소수점
real real.nan 사용 가능한 가장 큰 부동소수점 크기
ifloat ifloat.nan 허수 float (imaginary float)
idouble idouble.nan 허수 double
ireal ireal.nan 허수 real
cfloat cfloat.nan 두 float 값의 복소수
cdouble cdouble.nan 복소 double
creal creal.nan 복소 real
char '\xFF' 부호 없는 8비트 (UTF-8 코드 유닛)
wchar '\uFFFF' 부호 없는 16비트 (UTF-16 코드 유닛)
dchar '\U0000FFFF' 부호 없는 32비트 (UTF-32 코드 유닛)
typeof(null) null null 리터럴의 타입
typeof(*null) assert(0) 값이 없는 바텀 타입(bottom type)

기본 타입의 엔디언(endianness)은 ABI의 일부예요.

Implementation Defined: (구현 정의 사항) real 부동소수점 타입은 최소한 double 타입의 범위와 정밀도를 가져요. x86 CPU에서는 종종 x86 FPU가 지원하는 80비트 Extended Real 타입으로 구현돼요.

Note: 128비트 정수 타입인 centucent는 더 이상 사용되지 않아요(이제 deprecated).

Note: 복소·허수 타입 ifloat, idouble, ireal, cfloat, cdouble, crealstd.complex.Complex를 위해 더 이상 사용되지 않아요.

파생 데이터 타입 (Derived Data Types)

파생 데이터 타입은 다음 중 하나인 Type이에요:

  • TypeCtor Type
  • TypeCtor ( Type )
  • 타입 시퀀스
  • TypeSuffix를 가진 타입

TypeSuffix를 가진 타입은 다음과 같아요:

  • 포인터 (Pointers)
  • delegate 타입
  • 정적 배열 (Static Arrays)
  • 동적 배열 (Dynamic Arrays)
  • 연관 배열 (Associative Arrays)
  • 인덱스/슬라이스된 타입 시퀀스

예제:

int* p; // pointer
int[2] sa; // static array
int[] da; // dynamic array/slice

int[string] aa; // associative array
void function() fp; // function pointer

const(int) v = 2; // constant

import std.meta : AliasSeq;
AliasSeq!(int, string) tsi; // type sequence instance

Note: 파생 데이터 타입(derived data type)은 파생 클래스 타입(derived class type)과 구별돼요.

컴포넌트 타입 (Component Types)

파생 데이터 타입은 최소한 하나의 컴포넌트 타입을 가져요:

타입 컴포넌트 타입
TypeCtor(T) T
T* T
T[] T
T[AssignExpression] T
V[K] K, V
AliasSeq!(Params) Params

파생 데이터 타입에 타입 한정자(type qualifier)를 적용하면 그 컴포넌트 타입 모두에도 적용돼요.

포인터 (Pointers)

포인터 값은 메모리 주소예요. 타입 T에 대한 포인터는 타입 T의 인스턴스에 대한 참조인 값을 가져요. 이는 흔히 T에 대한 포인터라 불리며 그 타입은 T*예요. 가리키는 값에 접근하려면 * 역참조(dereference) 연산자를 사용해요:

int* p;

assert(p == null);
p = new int(5);
assert(p != null);

assert(*p == 5);
(*p)++;
assert(*p == 6);

포인터가 null 값을 가지면 유효한 데이터를 가리키고 있지 않아요. T에 대한 포인터가 역참조될 때는 null 값을 가지거나 타입 T의 유효한 인스턴스를 가리켜야 해요.

Implementation Defined: (구현 정의 사항) null 포인터가 역참조될 때의 동작. 전형적으로 프로그램이 중단(abort)돼요 — 이는 @safe 코드에서 필수예요.

Undefined Behavior: (미정의 동작) null이 아니면서 타입 T의 유효한 인스턴스를 가리키지 않는 포인터를 역참조하는 것.

기존 lvalue를 가리키도록 포인터를 설정하려면 & 주소 연산자(address of operator)를 사용해요:

int i = 2;
int* p = &i;

assert(p == &i);
assert(*p == 2);
*p = 4;
assert(i == 4);

. 연산자는 애그리게이트 타입 인스턴스에 대한 포인터를 역참조하고 그 멤버에 접근해요.

Note: C에서처럼 -> 연산자는 없어요.

Best Practices: (모범 사례) 포인터 주소가 탈출(escape)하지 않는다면 대신 ref 함수 매개변수나 ref 지역 변수를 사용하는 것을 고려하세요.

사용자 정의 타입 (User-Defined Types)

  • Enums
  • Structs and Unions
  • Classes
  • Interfaces

타입 변환 (Type Conversions)

포인터 변환 (Pointer Conversions)

포인터와 비포인터 사이의 캐스팅은 허용돼요. 일부 포인터 캐스팅은 @safe 코드에서 허용되지 않아요.

Best Practices: (모범 사례) 가비지 컬렉터가 할당한 데이터를 가리키는 포인터를 비포인터 타입으로 캐스팅하지 마세요.

암시적 변환 (Implicit Conversions)

암시적 변환은 필요에 따라 타입을 자동으로 변환하는 데 사용돼요.

  • bool은 정수로 암시적으로 변환돼요.
  • 정수는 .sizeof가 원본 타입과 같거나 더 큰 다른 정수 타입으로 암시적으로 변환돼요.
  • 정수는 float/double/real로 암시적으로 변환돼요.
  • float/double/real은 서로 암시적으로 변환돼요.
  • enum은 그 기본 타입(base type)으로 암시적으로 변환될 수 있어요 (하지만 그 반대 방향은 명시적 변환이 필요해요).
  • noreturn은 어떤 타입으로든 암시적으로 변환돼요.
  • struct는 자신의 AliasThis의 대상으로 암시적으로 변환될 수 있어요.
  • 클래스 인스턴스는 자신이 상속한 타입들로 암시적으로 변환돼요.
  • 인터페이스 인스턴스는 상속된 인터페이스들로 암시적으로 변환돼요.
  • 정적 배열은 동적 배열로 암시적으로 변환돼요.
  • 정적 배열, 동적 배열, 포인터는 void 요소 타입으로 암시적으로 변환돼요.
  • 클래스 요소 타입의 정적 배열, 동적 배열, 포인터는 암시적 변환이 있을 수 있어요.
  • 함수 포인터와 delegate는 공변(covariant) 타입으로 변환될 수 있어요.
void test(bool b)
{
    short s = b;
    int i = s;
    float f = i;

    noreturn n();
    i = n();
}

void f(int x) pure;
void function(int) fp = &f; // pure is covariant with non-pure

클래스 요소 변환 (Class Element Conversion)

클래스 타입 TU가 있고, UT를 상속한다고 할 때 다음 규칙이 적용돼요.

포인터 T*는 다음 두 조건을 모두 만족하면 U*로 암시적으로 변환될 수 있어요:

  • U가 변경 가능(mutable)하지 않을 것
  • UT와 호환되는 타입 한정자(type qualifiers)를 가질 것

마찬가지로 동적 배열 T[]는 다음 두 조건을 모두 만족하면 U[]로 암시적으로 변환될 수 있어요:

  • U가 mutable하지 않을 것
  • UT와 호환되는 타입 한정자를 가질 것

정적 배열 T[dim]은 다음 중 하나를 만족하면 U[dim]으로 암시적으로 변환될 수 있어요:

  • UT가 모두 mutable일 것
  • UT와 호환되는 타입 한정자를 가질 것
class C; // inherits from Object

void test()
{
    C* pc;
    //Object* po = pc; // error
    const(Object)* po = pc; // OK, `*po` is const
    immutable C* pic;
    po = pic;

    C[1] sc;
    Object[1] so = sc; // OK, `so` can be mutable

    C[] ac = sc;
    //Object[] ao = ac; // error
    const Object[] ao = ac;
}

Rationale: (근거) 이 규칙들은 파생 클래스 요소가 기본 클래스 인스턴스로 덮어써지는 것을 방지해요.

함수 포인터 변환 (Function Pointer Conversions)

함수 포인터 타입 Source는 다음 조건에서 다른 타입 Target으로 암시적으로 변환돼요:

  • 반환 타입이 Target의 것과 공변(covariant)일 것
  • 함수 속성이 Target의 것과 공변일 것
  • 각 매개변수가 Target의 해당 매개변수와 반변(contravariant)일 것

원본 타입이 대상 타입보다 더 제한적이면 변환은 공변(covariant)이에요. 다음 함수 포인터 타입 변환은 공변이에요:

Source 타입 Target 타입 조건
pure FP FP (함수 포인터)
nothrow FP FP
@nogc FP FP
@safe FP @trusted FP 또는 @system FP
S function(Args) B function(Args) SB의 하위 타입이고 두 함수 포인터 모두 ref가 아닐 것
// removing function attributes
pure nothrow @nogc @safe int f0();
int function() fp0 = &f0; // OK, calling fp0 can't violate f0's attributes
static assert(!is(typeof(fp0) : typeof(&f0))); // cannot add attributes to fp0

// qualifying return type
int* f1();
const(int*) function() fp1 = &f1; // OK, const(int*) is a subtype of int*
static assert(!is(typeof(fp1) : typeof(&f1))); // cannot unqualify return type of fp1

// returning a base type
int* f2();
void* function() fp2 = &f2; // OK, void* is a subtype of int*
static assert(!is(typeof(fp2) : typeof(&f2))); // fp2 cannot return subtype

원본 타입이 대상 타입보다 더 일반적이면 변환은 반변(contravariant)이에요. 다음 변환은 반변이에요:

Source 타입 Target 타입 조건
@trusted FP @safe FP 또는 @system FP
R function(TypeCtors(P)) R function(P) P가 참조 타입이고 is(P : TypeCtors(P))
R function(scope P) R function(P)

Note: const 매개변수는 mutable 매개변수보다 더 일반적이에요. 왜냐하면 const 매개변수는 mutable 인자와 const 인자 모두를 받아들이기 때문이에요.

// unqualifying a parameter
void f3(const int*);
void function(int*) fp3 = &f3; // OK
static assert(!is(typeof(fp3) : typeof(&f3))); // cannot qualify parameter

정수 승격 (Integer Promotions)

정수 승격(Integer Promotions)은 다음 타입들의 변환이에요:

정수 승격 from → to
bool int
byte int
ubyte int
short int
ushort int
char int
wchar int
dchar uint

enum이 왼쪽 열의 타입 중 하나를 기본 타입으로 가지면 오른쪽 열의 타입으로 변환돼요. 정수 승격은 이항 표현식의 각 피연산자에 적용돼요:

void fun()
{
    byte a;
    auto b = a + a;
    static assert(is(typeof(b) == int));
    // error: can't implicitly convert expression of type int to byte:
    //byte c = a + a;

    ushort d;
    // error: can't implicitly convert expression of type int to ushort:
    //d = d * d;
    int e = d * d; // OK
    static assert(is(typeof(int() * d) == int));

    dchar f;
    static assert(is(typeof(f - f) == uint));
}

Rationale: (근거) 현대 아키텍처에서는 단일 변수에 대해 32비트 정수 연산이 더 작은 정수 타입보다 종종 더 빨라요. 승격(promotion)은 작은 정수 타입에서 더 흔한 우발적 오버플로우를 피하는 데 도움이 돼요.

일반 산술 변환 (Usual Arithmetic Conversions)

일반 산술 변환은 이항 연산자의 피연산자들을 공통 타입으로 변환해요. 피연산자들은 이미 산술 타입이어야 해요. 기본 타입을 기준으로 다음 규칙이 순서대로 적용돼요:

  1. 어느 한 피연산자가 real이면 다른 피연산자가 real로 변환돼요.
  2. 그렇지 않고 어느 한 피연산자가 double이면 다른 피연산자가 double로 변환돼요.
  3. 그렇지 않고 어느 한 피연산자가 float이면 다른 피연산자가 float로 변환돼요.
  4. 그렇지 않으면 위의 정수 승격이 각 피연산자에 수행되고, 이어서:
    • 둘 다 같은 타입이면 더 이상의 변환은 없어요.
    • 둘 다 signed이거나 둘 다 unsigned이면 더 작은 타입이 더 큰 타입으로 변환돼요.
    • signed 타입이 unsigned 타입보다 크면 unsigned 타입이 signed 타입으로 변환돼요.
    • 그 외에는 signed 타입이 unsigned 타입으로 변환돼요.

Rationale: (근거) 위 규칙들은 C99를 따르며, C에서 코드를 이식(porting)하기를 더 쉽게 만들어요.

예제 — signed/unsigned 변환:

int i;
uint u;
static assert(is(typeof(i + u) == uint));
static assert(is(typeof(short() + u) == uint));
static assert(is(typeof(ulong() + i) == ulong));
static assert(is(typeof(long() - u) == long));
static assert(is(typeof(long() * ulong()) == ulong));

예제 — 부동소수점:

float f;
static assert(is(typeof(f + ulong()) == float));

double d;
static assert(is(typeof(f * d) == double));
static assert(is(typeof(real() / d) == real));
Enum 연산 (Enum Operations)

위 변환을 거친 후 피연산자 타입 중 하나 또는 둘 다 enum이면 결과 타입은 다음과 같이 결정돼요:

  1. 피연산자들이 같은 타입이면 결과는 그 타입이에요.
  2. 한 피연산자가 enum이고 다른 피연산자가 그 enum의 기본 타입이면 결과는 기본 타입이에요.
  3. 두 피연산자가 서로 다른 enum이면 결과는 둘 다에 공통인 가장 가까운 기본 타입이에요. 기본 타입이 더 가깝다는 것은 원래 타입에서 그곳에 도달하기 위한 기본 타입으로의 변환 시퀀스가 더 짧다는 뜻이에요.
enum E { a, b, c }
enum F { x, y }

void test()
{
    E e = E.a;
    e = e | E.c;
    //e = e + 4; // error, can't assign int to E
    int i = e + 4;
    e += 4; // OK, see below

    F f;
    //f = e | f; // error, can't assign int to F
    i = e | f;
}

Note: 위에서 e += 4는 컴파일되는데, 연산자 할당(operator assignment)이 e = cast(E)(e + 4)와 동등하기 때문이에요.

정수 타입 변환 (Integer Type Conversions)

타입 I의 정수는 J.sizeof >= I.sizeof일 때 다른 정수 타입 J로 암시적으로 변환돼요.

void f(byte b, ubyte ub, short s)
{
    b = ub; // OK, bit pattern same
    ub = b; // OK, bit pattern same
    s = b; // OK, widening conversion
    b = s; // error, implicit narrowing
}

정수 값은 정수 승격(integral promotion) 후 정수 비트 패턴을 표현할 수 없는 다른 타입으로 암시적으로 변환될 수 없어요. 예를 들어:

ubyte  u1 = -1; // error, -1 cannot be represented in a ubyte
ushort u2 = -1; // error, -1 cannot be represented in a ushort
uint   u3 = -1; // ok, -1 can be represented in an int, which can be converted to a uint
ulong  u4 = -1; // ok, -1 can be represented in a long, which can be converted to a ulong

부동소수점 타입 변환 (Floating Point Type Conversions)

  • 정수 타입은 부동소수점 타입으로 암시적으로 변환돼요.
  • 부동소수점 타입은 정수 타입으로 암시적으로 변환될 수 없어요.
void f(int i, float f)
{
    f = i; // OK
    i = f; // error
}
  • 복소 또는 허수 부동소수점 타입은 비복소 부동소수점 타입으로 암시적으로 변환될 수 없어요.
  • 비복소 부동소수점 타입은 허수 부동소수점 타입으로 암시적으로 변환될 수 없어요.

값 범위 전파 (Value Range Propagation)

타입 기반 암시적 변환 외에도, D는 정수 승격 후 일부 정수 표현식이 더 좁은 타입으로 암시적으로 변환되도록 허용해요. 이는 각 표현식에 대해 가능한 최소·최대 값 범위를 분석하여 동작해요. 그 값 범위가 더 좁은 대상 타입의 값 범위와 일치하거나 그 부분집합이면 암시적 변환이 허용돼요. 하위 표현식이 컴파일 타임에 알려져 있으면 값 범위를 더 좁힐 수 있어요.

void fun(char c, int i, ubyte b)
{
    // min is c.min + 100 > short.min
    // max is c.max + 100 < short.max
    short s = c + 100; // OK

    ubyte j = i & 0x3F; // OK, 0 ... 0x3F
    //ubyte k = i & 0x14A; // error, 0x14A > ubyte.max
    ushort k = i & 0x14A; // OK

    k = i & b; // OK, 0 ... b.max
    //b = b + b; // error, b.max + b.max > b.max
    s = b + b; // OK, 0 ... b.max + b.max
}

구현은 변경 가능한(mutable) 변수에 대한 가능한 값 범위를 추적하지 않는다는 점에 주의하세요:

void fun(int i)
{
    ushort s = i & 0xff; // OK
    // s is now assumed to be s.min ... s.max, not 0 ... 0xff
    //ubyte b = s; // error
    ubyte b = s & 0xff; // OK

    const int c = i & 0xff;
    // c's range is fixed and known
    b = c; // OK
}

void

void 값은 직접 접근할 수 없어요. void 타입은 주로 다음에 사용돼요:

  • 결과가 없는 함수의 반환 타입
  • 타입이 없는 포인터의 기본 타입 (Pointer Conversions 참조)
  • void 배열
  • void로 캐스팅해 값을 무시하는 것
  • TemplateDeclaration으로 해석되는 심볼의 타입
  • 비에포니머스(non-eponymous) TemplateInstance의 타입
void f();
static assert(is(typeof(f()) == void));

void* ptr;
static assert(is(typeof(ptr[0]) == void));

template t() {}
static assert(is(typeof(t) == void));
static assert(is(typeof(t!()) == void));

Rationale: (근거) 문법을 단순화하기 위해, 표현식은 템플릿이나 템플릿 인스턴스화로 해석될 수 있어요. 유효한 표현식은 유효한 타입을 가져야 해요.

void.sizeof는 1이에요 (0이 아님).

Rationale: (근거) C에서처럼 void* 산술이 동작하게 하려면 크기가 1이어야 해요. 또한 void 배열의 길이가 배열의 바이트 수와 같게 만들어 줘요.

bool

bool 타입은 true 또는 false 값만 담을 수 있는 byte 크기 타입이에요.

bool 타입의 피연산자를 직접 받아들이는 유일한 연산자는 &, |, ^, &=, |=, ^=, !, &&, ||, ?:이에요.

bool 값은 어떤 정수 타입으로든 암시적으로 변환될 수 있고, false는 0이 되고 true는 1이 돼요. 숫자 리터럴 0과 1은 각각 bool 값 false, true로 암시적으로 변환될 수 있어요. 표현식을 bool로 캐스팅하는 것은 ArithmeticType에서는 !=0을, 포인터나 참조 타입에서는 !=null을 검사하는 것을 의미해요. 자세한 내용은 Boolean Conversion을 참조하세요.

Undefined Behavior: (미정의 동작)

  • 0이나 1이 아닌 바이트 표현을 가진 값을 bool로 해석하는 것 (예: 겹친(overlapped) union 필드).
  • void로 초기화된 bool을 읽는 것.
bool b = 1; // int literal converted to `true`
assert(b);
assert(b + b == 2); // b promoted to int

byte i = 2;
//b = i; // Error
b = cast(bool) i; // OK, same as `i != 0`
assert(b);

bool* p = cast(bool*) &i; // unsafe cast
// `*p` holds 0x2, an invalid bool value
// reading `*p` is undefined behavior

함수 타입 (Function Types)

함수 타입은 다음 형태를 가져요:

StorageClassesopt Type Parameters FunctionAttributesopt

함수 타입은 Type 문법에 포함되지 않아요. 예를 들어 int(int) 같은 함수 타입은 alias될 수 있어요. 함수 타입은 타입 테스트(아래 참조)나 포인터의 대상 타입으로만 사용돼요. 함수 타입을 인스턴스화하는 것은 불법이에요. 대신 함수 포인터나 delegate를 사용할 수 있어요.

함수 포인터 (Function Pointers)

함수 포인터 타입은 다음 형태를 가져요:

Type function Parameters FunctionAttributesopt
void f(int);
alias Fun = void(int);
static assert(is(typeof(f) == Fun));
static assert(is(Fun* == void function(int)));

delegate

Type delegate Parameters MemberFunctionAttributesopt

delegate는 두 데이터 조각, 즉 컨텍스트 포인터와 함수 포인터의 애그리게이트예요. 유효한 delegate는 다음 중 하나를 가져요:

  • 객체 참조와 비정적 멤버 함수에 대한 포인터. 함수가 호출될 때 객체 참조가 this 포인터를 이뤄요.
  • 클로저(closure)에 대한 포인터와 중첩 함수에 대한 포인터.

delegate의 .ptr 속성은 컨텍스트 포인터 값을 void*로 반환해요. delegate의 .funcptr 속성은 함수 포인터 값을 함수 타입으로 반환해요.

delegate는 함수 포인터와 유사하게 선언되고 초기화돼요:

void func(int) {}
void function(int) fp; // fp is a function pointer
void delegate(int) dg; // dg is a delegate to a function

class OB
{
    void member(int) {}
}

void main()
{
    OB o = new OB;

    fp = &func; // fp points to function `func`

    dg = &o.member; // dg is a delegate to object `o` and member function `member`
    assert(dg.ptr == cast(void*) o);
    assert(dg.funcptr == &OB.member);

    dg = (int i) { o.member(i); }; // dg holds a delegate literal with main's execution context
}

delegate는 정적 멤버 함수나 멤버가 아닌 함수로 초기화될 수 없어요.

delegate는 함수 포인터와 유사하게 호출돼요:

fp(3);   // call func(3)
dg(3);   // call o.member(3)

멤버 함수 포인터의 등가물은 익명 람다 함수를 사용해 구성할 수 있어요:

class C
{
    int a;
    int foo(int i) { return i + a; }
}

// mfp is the member function pointer
auto mfp = function(C self, int i) { return self.foo(i); };
auto c = new C();  // create an instance of C
mfp(c, 1);  // and call c.foo(1)

typeof

Typeof:
    typeof ( Expression )
    typeof ( return )

첫 번째 형태는 표현식의 타입을 제공해요. BasicType이 예상되는 어디에서나 사용할 수 있어요 (예: 선언 안). 하위 표현식(sub-expression)에서 PrimaryExpression으로도 유용해요. 예를 들어:

void func(int i)
{
    typeof(i) j;       // j is of type int
    typeof(3 + 6.0) x; // x is of type double

    // as part of a derived type:
    typeof(1)* p;
    static assert(is(typeof(p) == int*));
    typeof(p)[int] aa;
    static assert(is(typeof(aa) == int*[int]));

    auto d = cast(typeof(1.0)) i; // cast i to double
    static assert(is(typeof(d) == double));

    // as a sub-expression:
    static assert(typeof('c').sizeof == 1); // char.sizeof
    Exception[2] sa;
    Exception ex = new typeof(sa[0])("message"); // new Exception("message")
}

Expression은 평가되지 않고, 오직 타입을 생성하는 데만 사용돼요:

void main()
{
    int i = 1;
    typeof(++i) j; // j is declared to be an int, i is not incremented
    assert(i == 1);
}

Expression이 ValueSeq면, typeof는 각 요소의 타입을 포함하는 TypeSeq를 생성해요. typeof(null)은 null 리터럴의 타입을 얻는 데 유용해요.

Best Practices: (모범 사례) typeof는 제네릭 템플릿 코드 작성에 가장 유용해요.

특수 경우 (Special Cases)

  1. typeof(return)은 함수 스코프 안에 있을 때 그 함수의 반환 타입을 제공해요.
  2. typeof(this)는 멤버 함수가 아니어도, 비정적 멤버 함수에서 this가 무엇일지의 타입을 생성해요.
  3. 유사하게 typeof(super)는 비정적 멤버 함수에서 super가 무엇일지의 타입을 생성해요.

표현식이 Property Function이면 typeof는 그 반환 타입을 제공해요.

struct S
{
    @property int foo() { return 1; }
}
typeof(S.foo) n;  // n is declared to be an int

믹스인 타입 (Mixin Types)

MixinType:
    mixin ( ArgumentList )

ArgumentList의 각 AssignExpression은 컴파일 타임에 평가되고, 결과는 문자열로 표현될 수 있어야 해요. 결과 문자열들은 연결되어 하나의 문자열을 이뤄요. 문자열의 텍스트 내용은 유효한 Type으로 컴파일 가능해야 하며 그렇게 컴파일돼요.

void test(mixin("int")* p) // int* p
{
    mixin("int")[] a;      // int[] a;
    mixin("int[]") b;      // int[] b;
}

앨리어스된 타입 (Aliased Types)

size_t

size_t는 부호 없는 정수 기본 타입 중 하나에 대한 alias이며, 모든 주소 가능한 메모리로의 오프셋을 표현하기에 충분히 큰 타입을 나타내요.

ptrdiff_t

ptrdiff_tsize_t와 같은 크기의 부호 있는 정수 기본 타입에 대한 alias예요.

string

string은 배열의 특수 경우예요.

noreturn

noreturn은 어떤 타입으로든, void를 포함해, 암시적으로 변환될 수 있는 바텀 타입(bottom type)이에요. noreturn 타입의 값은 절대 생성되지 않으며 컴파일러는 그런 코드를 그에 따라 최적화할 수 있어요.

noreturn.initassert(0)으로 낮아져요(lowers).

절대 반환하지 않는 함수는 반환 타입 noreturn을 가져요. 이는 예를 들어 무한 루프나 항상 예외를 던지는 것으로 인해 발생할 수 있어요. noreturn을 반환하는 함수는 다른 어떤 타입을 반환하는 함수와도 공변(covariant)이에요.

noreturn abort(string message);

int function(string) fp = &abort; // OK

int example(int i)
{
    if (i < 0)
    {
        // abort does not return, so it doesn't need to produce an int
        int val = abort("less than zero");
    }
    // ternary expression's common type is still int
    return i != 0 ? 1024 / i : abort("calculation went awry.");
}

noreturntypeof(*null)로 정의돼요. 이는 null 리터럴을 역참조하는 것이 (전형적으로) 실행을 중단시키기 때문이에요.

더 알아보기