타입
타입 (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비트 정수 타입인
cent와ucent는 더 이상 사용되지 않아요(이제 deprecated).Note: 복소·허수 타입
ifloat,idouble,ireal,cfloat,cdouble,creal은std.complex.Complex를 위해 더 이상 사용되지 않아요.
파생 데이터 타입 (Derived Data Types)
파생 데이터 타입은 다음 중 하나인 Type이에요:
TypeCtor TypeTypeCtor ( 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)
클래스 타입 T와 U가 있고, U가 T를 상속한다고 할 때 다음 규칙이 적용돼요.
포인터 T*는 다음 두 조건을 모두 만족하면 U*로 암시적으로 변환될 수 있어요:
U가 변경 가능(mutable)하지 않을 것U가T와 호환되는 타입 한정자(type qualifiers)를 가질 것
마찬가지로 동적 배열 T[]는 다음 두 조건을 모두 만족하면 U[]로 암시적으로 변환될 수 있어요:
U가 mutable하지 않을 것U가T와 호환되는 타입 한정자를 가질 것
정적 배열 T[dim]은 다음 중 하나를 만족하면 U[dim]으로 암시적으로 변환될 수 있어요:
U와T가 모두 mutable일 것U가T와 호환되는 타입 한정자를 가질 것
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) |
S가 B의 하위 타입이고 두 함수 포인터 모두 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)
일반 산술 변환은 이항 연산자의 피연산자들을 공통 타입으로 변환해요. 피연산자들은 이미 산술 타입이어야 해요. 기본 타입을 기준으로 다음 규칙이 순서대로 적용돼요:
- 어느 한 피연산자가
real이면 다른 피연산자가real로 변환돼요. - 그렇지 않고 어느 한 피연산자가
double이면 다른 피연산자가double로 변환돼요. - 그렇지 않고 어느 한 피연산자가
float이면 다른 피연산자가float로 변환돼요. - 그렇지 않으면 위의 정수 승격이 각 피연산자에 수행되고, 이어서:
- 둘 다 같은 타입이면 더 이상의 변환은 없어요.
- 둘 다 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이면 결과 타입은 다음과 같이 결정돼요:
- 피연산자들이 같은 타입이면 결과는 그 타입이에요.
- 한 피연산자가 enum이고 다른 피연산자가 그 enum의 기본 타입이면 결과는 기본 타입이에요.
- 두 피연산자가 서로 다른 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)
typeof(return)은 함수 스코프 안에 있을 때 그 함수의 반환 타입을 제공해요.typeof(this)는 멤버 함수가 아니어도, 비정적 멤버 함수에서this가 무엇일지의 타입을 생성해요.- 유사하게
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_t는 size_t와 같은 크기의 부호 있는 정수 기본 타입에 대한 alias예요.
string
string은 배열의 특수 경우예요.
noreturn
noreturn은 어떤 타입으로든, void를 포함해, 암시적으로 변환될 수 있는 바텀 타입(bottom type)이에요. noreturn 타입의 값은 절대 생성되지 않으며 컴파일러는 그런 코드를 그에 따라 최적화할 수 있어요.
noreturn.init는 assert(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.");
}
noreturn은 typeof(*null)로 정의돼요. 이는 null 리터럴을 역참조하는 것이 (전형적으로) 실행을 중단시키기 때문이에요.