타입
타입 (Types)
D는 정적 타입 언어예요. D 프로그램을 짜다 보면 "이 값은 어떤 타입이지?" 하고 따져볼 일이 꽤 많아요. 이번 장에서는 D가 다루는 모든 타입의 종류와, 타입이 변환되는 규칙을 한 번에 정리해 봅니다.
본문
D 언어에서 타입은 크게 세 갈래로 나뉘어요. 기본 데이터 타입(Basic Data Types)은 잎(leaf)처럼 더 작은 조각으로 쪼개지지 않는 타입이고요, 파생 데이터 타입(Derived Data Types)은 기본 타입 위에 쌓아 만드는 타입이에요. 마지막으로 사용자 정의 타입(User-Defined Types)은 기본 타입과 파생 타입을 조합해 만드는 덩어리 타입이죠.
문법 (Grammar)
D는 정적으로 타입이 결정됩니다. 모든 표현식(expression)은 타입을 가지거든요. 타입은 표현식이 담을 수 있는 값을 제한하고, 그 값들에 가해지는 연산의 의미를 결정해요.
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)
D가 기본으로 제공하는 데이터 타입 목록이에요. 각 타입의 키워드와 기본 초기화 값(.init), 그리고 한 줄 설명을 정리한 표예요.
기본 데이터 타입
| 키워드 | 기본 초기화 값 (.init) |
설명 |
|---|---|---|
void |
기본 초기화 값 없음 | void는 값을 가지지 않아요 |
bool |
false |
참/거짓(boolean) 값 |
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 |
허수(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의 일부예요.
구현 정의: real 부동소수점 타입은 최소한 double 타입의 범위와 정밀도를 가져요. x86 CPU에서는 x86 FPU가 지원하는 80비트 확장 실수(Extended Real) 타입으로 구현되는 경우가 많고요.
참고: 128비트 정수 타입인 cent와 ucent는 더 이상 권장되지 않아요(deprecated).
참고: 복소수·허수 타입인 ifloat, idouble, ireal, cfloat, cdouble, creal은 std.complex.Complex로 대체되며 권장되지 않게 되었어요.
파생 데이터 타입 (Derived Data Types)
파생 데이터 타입은 다음 중 하나에 해당하는 Type이에요.
TypeCtor Type형태TypeCtor ( Type )형태- 타입 시퀀스(type sequence)
- TypeSuffix를 가지는 경우
TypeSuffix를 가지는 타입은 다음과 같아요.
- 포인터(Pointer)
- 델리게이트 타입(Delegate Type)
- 정적 배열(Static Array)
- 동적 배열(Dynamic Array)
- 연관 배열(Associative Array)
- 인덱스/슬라이스된 타입 시퀀스
예시를 볼게요.
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
참고: 파생 데이터 타입은 "클래스에서 파생된 타입"(derived class type)과는 다른 개념이에요.
구성 요소 타입 (Component Types)
파생 데이터 타입은 최소한 하나의 구성 요소 타입(component type)을 가져요.
| 타입 | 구성 요소 타입 |
|---|---|
TypeCtor(T) |
T |
T* |
T |
T[] |
T |
T[AssignExpression] |
T |
V[K] |
K, V |
AliasSeq!(Params) |
Params |
파생 데이터 타입에 타입 한정자(type qualifier)를 적용하면 그 구성 요소 타입 전체에도 함께 적용돼요. 더 자세한 내용은 "파생 데이터 타입의 패턴 매칭"을 참고하세요.
포인터 (Pointers)
포인터 값은 메모리 주소예요. 타입 T에 대한 포인터는 타입 T의 인스턴스를 가리키는 참조를 값으로 가져요. 보통 T에 대한 포인터(pointer to 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의 유효한 인스턴스를 가리켜야 해요.
구현 정의: null 포인터를 역참조했을 때의 동작이에요. 보통은 프로그램이 중단(abort)되는데, @safe 코드에서는 그렇게 동작해야 해요.
정의되지 않은 동작: null이 아니면서 타입 T의 유효한 인스턴스를 가리키지 않는 포인터를 역참조하는 경우예요.
포인터가 기존 lvalue를 가리키도록 만들려면 & 주소(address of) 연산자를 사용해요.
int i = 2;
int* p = &i;
assert(p == &i);
assert(*p == 2);
*p = 4;
assert(i == 4);
. 연산자는 집합(aggregate) 타입 인스턴스를 가리키는 포인터를 역참조하고 그 멤버에 접근해요.
참고: C에서처럼 -> 연산자는 없어요.
더 알아보기:
- 포인터 산술(Pointer Arithmetic)
- 포인터 슬라이싱(Pointer Slicing)
- 포인터 변환(Pointer Conversions)
권장 사항: 포인터 주소가 함수 밖으로 빠져나가지 않을 거라면, 포인터 대신 ref 함수 매개변수나 ref 지역 변수를 쓰는 걸 고려해 보세요.
사용자 정의 타입 (User-Defined Types)
- 열거형(Enum)
- 구조체와 공용체(Struct와 Union)
- 클래스(Class)
- 인터페이스(Interface)
타입 변환 (Type Conversions)
CastExpression
더 알아보기:
- 포인터 변환(Pointer Conversions)
포인터 변환 (Pointer Conversions)
포인터와 비포인터 사이의 캐스팅은 허용돼요. 다만 일부 포인터 캐스팅은 @safe 코드에서 허용되지 않아요.
권장 사항: 가비지 컬렉터가 할당한 데이터를 가리키는 포인터는 비포인터 타입으로 캐스팅하지 마세요.
암묵적 변환 (Implicit Conversions)
암묵적 변환은 필요에 따라 타입을 자동으로 변환할 때 사용돼요.
bool은 정수로 암묵적으로 변환돼요.- 정수는
.sizeof가 원본 타입과 같거나 더 큰 다른 정수 타입으로 암묵적으로 변환돼요. - 정수는
float/double/real로 암묵적으로 변환돼요. float/double/real은 서로 암묵적으로 변환돼요.- 열거형은 자신의 기반 타입(base type)으로 암묵적으로 변환될 수 있어요. 다만 그 반대 방향은 명시적 변환이 필요해요.
noreturn은 어떤 타입으로든 암묵적으로 변환돼요.- 구조체는 자기 AliasThis의 대상 타입으로 암묵적으로 변환될 수 있어요.
- 클래스 인스턴스는 자신이 상속한 타입들로 암묵적으로 변환돼요.
- 인터페이스 인스턴스는 상속한 어떤 인터페이스로든 암묵적으로 변환돼요.
- 정적 배열은 동적 배열로 암묵적으로 변환돼요.
- 정적 배열, 동적 배열, 포인터는 요소 타입이
void인 타입으로 암묵적으로 변환돼요. - 클래스 요소 타입을 가진 정적 배열, 동적 배열, 포인터는 암묵적 변환이 가능할 수 있어요.
- 함수 포인터와 델리게이트는 공변(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
더 알아보기:
- 암묵적 한정자 변환(Implicit Qualifier Conversions)
- 정수 캐스팅(Casting Integers)
클래스 요소 변환 (Class Element Conversion)
클래스 타입 T와 U가 있고, U가 T를 상속한다고 해 볼게요. 그때 다음 규칙이 적용돼요.
포인터 T*는 다음 두 조건을 모두 만족하면 U*로 암묵적으로 변환될 수 있어요.
U가 가변(mutable)이 아닐 것U가T와 호환되는 타입 한정자를 가질 것
마찬가지로 동적 배열 T[]는 다음 두 조건을 모두 만족하면 U[]로 암묵적으로 변환될 수 있어요.
U가 가변이 아닐 것U가T와 호환되는 타입 한정자를 가질 것
정적 배열 T[dim]은 다음 중 하나라도 만족하면 U[dim]으로 암묵적으로 변환될 수 있어요.
U와T가 모두 가변일 것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;
}
이유: 이 규칙 덕분에 파생 클래스 요소가 기반 클래스 인스턴스로 덮어써지지 않는 거예요.
함수 포인터 변환 (Function Pointer Conversions)
함수 포인터 타입 Source는 다음 조건을 모두 만족하면 다른 타입 Target으로 암묵적으로 변환돼요.
- 반환 타입이
Target의 반환 타입과 *공변(covariant)*일 것 - 함수 속성이
Target의 함수 속성과 공변일 것 - 각 매개변수가
Target의 대응 매개변수와 *반변(contravariant)*일 것
원본 타입이 대상 타입보다 더 제한적일 때 그 변환은 공변이라고 해요. 다음 함수 포인터 타입 변환은 공변이에요.
| 원본 타입 | 대상 타입 | 조건 |
|---|---|---|
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
원본 타입이 대상 타입보다 더 일반적(일반화)일 때 그 변환은 반변이라고 해요. 다음 변환은 반변이에요.
| 원본 타입 | 대상 타입 | 조건 |
|---|---|---|
@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) |
참고: const 매개변수는 가변 매개변수보다 더 일반적이에요. 가변 인자와 const 인자를 모두 받아들일 수 있으니까요. return 속성의 가변성(variance)도 함께 보세요.
// unqualifying a parameter
void f3(const int*);
void function(int*) fp3 = &f3; // OK
static assert(!is(typeof(fp3) : typeof(&f3))); // cannot qualify parameter
더 알아보기:
is(T : U)override메서드 공변성(covariance)
정수 승격 (Integer Promotions)
정수 승격은 다음 타입들의 변환을 말해요.
정수 승격
| from | to |
|---|---|
bool |
int |
byte |
int |
ubyte |
int |
short |
int |
ushort |
int |
char |
int |
wchar |
int |
dchar |
uint |
열거형의 기반 타입이 왼쪽 열의 타입 중 하나라면, 그 열거형은 오른쪽 열의 타입으로 변환돼요.
정수 승격은 이항 표현식의 각 피연산자에 적용돼요.
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));
}
이유:
- 현대 아키텍처에서는 단일 변수에 대해 32비트 정수 연산이 더 작은 정수 타입보다 자주 더 빨라요.
- 승격 덕분에 작은 정수 타입에서 더 흔하게 발생하는 우발적 오버플로를 피할 수 있어요.
보통 산술 변환 (Usual Arithmetic Conversions)
보통 산술 변환은 이항 연산자의 피연산자들을 공통 타입으로 변환해요. 피연산자들은 이미 산술 타입이어야 해요. 기반 타입을 기준으로 다음 규칙이 순서대로 적용돼요.
- 어느 한 피연산자가
real이면, 다른 피연산자도real로 변환돼요. - 그게 아니고 어느 한 피연산자가
double이면, 다른 피연산자도double로 변환돼요. - 그게 아니고 어느 한 피연산자가
float이면, 다른 피연산자도float으로 변환돼요. - 그게 아니라면 위의 정수 승격을 각 피연산자에 적용하고, 그 다음에 아래 규칙을 따릅니다.
둘 다 같은 타입이면 더 이상의 변환은 없어요.
둘 다 부호가 있거나 둘 다 부호가 없으면, 더 작은 타입이 더 큰 타입으로 변환돼요.
부호 있는 타입이 부호 없는 타입보다 크면, 부호 없는 타입이 부호 있는 타입으로 변환돼요.
그 외에는 부호 있는 타입이 부호 없는 타입으로 변환돼요.
이유: 위 규칙은 C99를 따르고 있어서, C에서 가져온 코드를 옮기기가 더 쉬워져요.
예시: 부호 있는/부호 없는 변환.
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 Operations)
위 변환을 거친 뒤에도 피연산자 타입 중 하나 또는 둘 다 열거형이면, 결과 타입은 다음과 같이 결정돼요.
- 피연산자가 같은 타입이면, 결과도 그 타입이 돼요.
- 한 피연산자가 열거형이고 다른 하나가 그 열거형의 기반 타입이면, 결과는 기반 타입이 돼요.
- 두 피연산자가 서로 다른 열거형이면, 결과는 둘 다에 가장 가까운 공통 기반 타입이 돼요. 기반 타입이 "가깝다"는 건 원래 타입에서 그 기반 타입까지 가는 변환 경로가 더 짧다는 뜻이에요.
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;
}
참고: 위에서 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
}
정수 값은 정수 승격 후에도 그 정수의 비트 패턴을 표현할 수 없는 다른 타입으로는 암묵적으로 변환되지 않아요. 예를 들어 볼게요.
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
}
한 가지 짚고 넘어갈 점이 있어요. 구현은 가변 변수에 대해 가능한 값의 범위를 추적하지 않아요.
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
}
- 더 자세한 내용은 dmc 아티클을 참고하세요.
- 참고: https://en.wikipedia.org/wiki/Value_range_analysis
void
void 값은 직접 접근할 수 없어요. void 타입은 주로 다음 용도로 쓰여요.
- 결과가 없는 함수의 반환 타입
- 타입이 없는 포인터의 기반 타입 — 포인터 변환 참고
- void 배열
void로 캐스팅해서 값을 무시하기- TemplateDeclaration으로 해석되는 심볼의 타입
- 비-동명(非-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));
이유: 문법을 단순하게 만들기 위해, 표현식이 템플릿이나 템플릿 인스턴스화로 해석될 수 있어요. 유효한 표현식은 유효한 타입을 가져야 하니까요.
void.sizeof는 1(0이 아님)이에요.
이유: C에서처럼 void* 산술이 동작하려면 크기가 1이어야 해요. 또한 void 배열의 길이가 그 배열의 바이트 수와 같아지는 효과도 있어요.
bool
bool 타입은 한 바이트 크기의 타입으로, true 또는 false 값만 담을 수 있어요.
bool 타입의 피연산자를 직접 받아들이는 연산자는 &, |, ^, &=, |=, ^=, !, &&, ||, ?:뿐이에요.
bool 값은 false가 0, true가 1이 되면서 어떤 정수 타입으로든 암묵적으로 변환될 수 있어요.
숫자 리터럴 0과 1은 각각 bool 값 false와 true로 암묵적으로 변환될 수 있어요. 표현식을 bool로 캐스팅한다는 것은 ArithmeticType에 대해 !=0을, 포인터나 참조 타입에 대해 !=null을 검사하는 것을 뜻해요. 자세한 내용은 불리언 변환(Boolean Conversion)을 참고하세요.
정의되지 않은 동작:
- 0이나 1이 아닌 바이트 표현을 가진 값을
bool로 해석하는 경우 (예: 겹친(overlapped) 공용체 필드) 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)을 만들 수 있지만요.
함수 타입은 타입 검사(아래 참고)나 포인터의 대상 타입으로만 사용돼요. 함수 타입을 인스턴스화하는 것은 불법이라서, 대신 함수 포인터나 델리게이트를 사용해야 해요.
함수 포인터 (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)));
함수 포인터(Function Pointers)를 참고하세요.
델리게이트 (Delegates)
Type delegate Parameters MemberFunctionAttributesopt
델리게이트는 두 조각의 데이터, 즉 컨텍스트 포인터(context pointer)와 함수 포인터를 묶은 덩어리예요. 유효한 델리게이트는 다음 중 하나를 담고 있어요.
- 객체 참조와 비정적 멤버 함수에 대한 포인터. 함수가 호출될 때 그 객체 참조가
this포인터를 이뤄요. - 클로저(closure)에 대한 포인터와 중첩 함수에 대한 포인터.
델리게이트의 .ptr 속성은 컨텍스트 포인터 값을 void*로 돌려줘요.
델리게이트의 .funcptr 속성은 함수 포인터 값을 함수 타입으로 돌려줘요.
델리게이트는 함수 포인터와 비슷하게 선언하고 초기화해요.
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
}
델리게이트는 정적 멤버 함수나 비멤버 함수로는 초기화할 수 없어요.
델리게이트는 함수 포인터와 유사하게 호출해요.
fp(3); // call func(3)
dg(3); // call o.member(3)
참고:
- 델리게이트와 클로저(Delegates and Closures)
- 메서드 델리게이트(Method Delegates)
멤버 함수 포인터에 해당하는 것은 익명 람다 함수를 써서 만들 수 있어요.
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이 필요한 곳이라면 어디든 사용할 수 있는데, 예를 들어 선언에서 쓰죠. 또한 하위 표현식에서 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 리터럴의 타입을 얻을 때 유용해요.
권장 사항: Typeof는 제네릭 템플릿 코드를 작성할 때 가장 유용해요.
특수한 경우 (Special Cases)
typeof(return)은 함수 스코프 안에 있을 때 그 함수의 반환 타입을 알려줘요.typeof(this)는 멤버 함수 안이 아니더라도, 비정적 멤버 함수에서this의 타입이 무엇일지를 생성해요.- 마찬가지로
typeof(super)는 비정적 멤버 함수에서super의 타입이 무엇일지를 생성해요.
class A { }
class B : A
{
typeof(this) x; // x is declared to be a B
typeof(super) y; // y is declared to be an A
}
struct C
{
static typeof(this) z; // z is declared to be a C
typeof(super) q; // error, no super struct for C
}
typeof(this) r; // error, no enclosing struct or class
표현식이 프로퍼티 함수(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는 부호 없는 정수 기본 타입 중 하나의 별칭이에요. 모든 주소 가능한 메모리에 대한 오프셋을 표현할 수 있을 만큼 큰 타입을 나타내죠.
ptrdiff_t
ptrdiff_t는 size_t와 같은 크기의 부호 있는 정수 기본 타입의 별칭이에요.
string
string은 배열의 특수한 경우예요.
noreturn
noreturn은 바닥 타입(bottom type)으로, void를 포함해 어떤 타입으로든 암묵적으로 변환될 수 있어요. noreturn 타입의 값은 절대 생성되지 않으며, 컴파일러는 그런 코드를 그에 맞게 최적화할 수 있어요.
noreturn.init는 assert(0)로 낮춰져요.
결코 반환하지 않는 함수는 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 리터럴을 역참조하면 (보통) 실행이 멈추기 때문이에요.
더 알아보기: ThrowExpression
더 알아보기 (Learn more)
- D 언어 사양의 다른 타입 관련 챕터들이 궁금하다면 Declarations와 Properties 페이지를 함께 보세요.
- 타입 변환과 관련된 더 깊은 내용은 Type Conversions 항목과 Implicit Qualifier Conversions 문서를 참고하세요.
- 연관 배열, 동적 배열 등 배열 계열 타입은 Arrays 챕터에서 다뤄요.
- 델리게이트와 클로저의 자세한 동작은 Delegates and Closures 문서를 참고하세요.