선언

선언 (Declarations)

Declaration 은 함수 본문 안에서 DeclarationStatement로 사용할 수 있고, DeclDef에 포함되므로 함수 밖에서도 사용할 수 있어요.

출처: Declarations

본문

문법 (Grammar)

[Declaration]():
    [*FuncDeclaration*](../spec/function.html#FuncDeclaration)
    [*VarDeclarations*](#VarDeclarations)
    [*AliasDeclaration*](#AliasDeclaration)
    [*AggregateDeclaration*](#AggregateDeclaration)
    [*EnumDeclaration*](../spec/enum.html#EnumDeclaration)
    [*ImportDeclaration*](../spec/module.html#ImportDeclaration)
    [*ConditionalDeclaration*](../spec/version.html#ConditionalDeclaration)
    [*StaticForeachDeclaration*](../spec/version.html#StaticForeachDeclaration)
    [*StaticAssert*](../spec/version.html#StaticAssert)
    [*TemplateDeclaration*](../spec/template.html#TemplateDeclaration)
    [*TemplateMixinDeclaration*](../spec/template-mixin.html#TemplateMixinDeclaration)
    [*TemplateMixin*](../spec/template-mixin.html#TemplateMixin)

집계 (Aggregates)

[AggregateDeclaration]():
    [*ClassDeclaration*](../spec/class.html#ClassDeclaration)
    [*InterfaceDeclaration*](../spec/interface.html#InterfaceDeclaration)
    [*StructDeclaration*](../spec/struct.html#StructDeclaration)
    [*UnionDeclaration*](../spec/struct.html#UnionDeclaration)

변수 선언 (Variable Declarations)

[VarDeclarations]():
    [*StorageClasses*](#StorageClasses)opt [*BasicType*](../spec/type.html#BasicType) [*TypeSuffixes*](../spec/type.html#TypeSuffixes)opt [*IdentifierInitializers*](#IdentifierInitializers) ;
    [*AutoDeclaration*](#AutoDeclaration)
    [*TuplePattern*](#TuplePattern) = [*AssignExpression*](../spec/expression.html#AssignExpression) ;

[IdentifierInitializers](): [](#DeclaratorIdentifierList)
    [*IdentifierInitializer*](#IdentifierInitializer)
    [*IdentifierInitializer*](#IdentifierInitializer) , *IdentifierInitializers*

[IdentifierInitializer](): [](#DeclaratorIdentifier)
    [*Identifier*](../spec/lex.html#Identifier)
    [*Identifier*](../spec/lex.html#Identifier) [*TemplateParameters*](../spec/template.html#TemplateParameters)opt = [*Initializer*](#Initializer)
    [*BitfieldDeclarator*](#BitfieldDeclarator)
    [*BitfieldDeclarator*](#BitfieldDeclarator) = [*Initializer*](#Initializer)

[BitfieldDeclarator]():
    : [*AssignExpression*](../spec/expression.html#AssignExpression)
    [*Identifier*](../spec/lex.html#Identifier) : [*ConditionalExpression*](../spec/expression.html#ConditionalExpression)

[Declarator](): [](#)
    [*TypeSuffixes*](../spec/type.html#TypeSuffixes)opt [*Identifier*](../spec/lex.html#Identifier)

참고: 선언 문법.

저장 클래스 (Storage Classes)

타입 클래스 vs 저장 클래스를 참고하세요.

[StorageClasses]():
    [*StorageClass*](#StorageClass)
    [*StorageClass*](#StorageClass) *StorageClasses*

[StorageClass]():
    [*LinkageAttribute*](../spec/attribute.html#LinkageAttribute)
    [*AlignAttribute*](../spec/attribute.html#AlignAttribute)
    [*AtAttribute*](../spec/attribute.html#AtAttribute)
    [deprecated](../spec/attribute.html#deprecated)
    [enum](../spec/enum.html#single_member)
    [static](../spec/attribute.html#static)
    [extern](#extern)
    [abstract](../spec/class.html#abstract)
    [final](../spec/attribute.html#final)
    [override](../spec/function.html#virtual-functions)
    [synchronized](../spec/class.html#synchronized-methods)
    [auto](#auto-declaration)
    [scope](../spec/attribute.html#scope)
    [const](../spec/const3.html)
    [immutable](../spec/const3.html)
    [inout](../spec/const3.html#inout)
    [shared](../spec/const3.html#shared)
    [__gshared](../spec/attribute.html#gshared)
    [*Property*](../spec/attribute.html#Property)
    [nothrow](../spec/function.html#nothrow-functions)
    [pure](../spec/function.html#pure-functions)
    [ref](#ref-storage)

선언 문법 (Declaration Syntax)

선언 문법은 일반적으로 배열을 포함해 오른쪽에서 왼쪽으로 읽어요.

int x;    // x is an int
int* x;   // x is a pointer to int
int** x;  // x is a pointer to a pointer to int

int[] x;  // x is an array of ints
int*[] x; // x is an array of pointers to ints
int[]* x; // x is a pointer to an array of ints

int[3] x;     // x is a static array of 3 ints
int[3][5] x;  // x is a static array of 5 static arrays of 3 ints
int[3]*[5] x; // x is a static array of 5 pointers to static arrays of 3 ints

Pointers, Arrays, TypeSuffix를 참고하세요.

함수 포인터 (Function Pointers)

함수 포인터는 function 키워드를 사용해 선언돼요.

int function(char) x; // x is a pointer to
                     // a function taking a char argument
                     // and returning an int
int function(char)[] x; // x is an array of
                     // pointers to functions
                     // taking a char argument
                     // and returning an int

C 스타일 선언 (C-Style Declarations)

C 스타일 배열, 함수 포인터, 배열에 대한 포인터 선언은 지원되지 않아요. 다음 C 선언들은 비교를 위해서만 존재해요.

int x[3];          // C static array of 3 ints
int x[3][5];       // C static array of 3 arrays of 5 ints

int (*x[5])[3];    // C static array of 5 pointers to static arrays of 3 ints
int (*x)(char);    // C pointer to a function taking a char argument
                   // and returning an int
int (*[] x)(char); // C array of pointers to functions
                   // taking a char argument and returning an int

이론적 근거(Rationale):

  • D에서 타입은 C와 달리 오른쪽에서 왼쪽으로 읽기 쉽게 되어 있어요. C에서는 괄호가 때때로 필요하고, 시계방향/나선 규칙(clockwise/spiral rule)을 사용해 반복적으로 읽어야 해요.
  • C 함수 포인터 선언 a(*b)(c)의 경우, C 파서는 모호함 없이 파싱하기 위해 타입 조회를 시도해야 해요 — 함수 포인터를 반환하는 a라는 함수에 대한 호출일 수 있고 즉시 호출되기 때문이에요. D 함수 포인터 문법은 명확하며, 타입을 앞으로 선언(forward declare)할 필요를 피해 줘요.

여러 심볼 선언 (Declaring Multiple Symbols)

여러 심볼을 선언하는 선언에서는 모든 선언이 같은 타입이어야 해요.

int x, y;   // x and y are ints
int* x, y;  // x and y are pointers to ints
int[] x, y; // x and y are arrays of ints

이것은 C와 대조돼요.

int x, *y;  // x is an int, y is a pointer to int
int x[], y; // x is an array/pointer, y is an int

초기화 (Initialization)

Initializer 가 없으면 변수는 그 타입의 기본 .init 값으로 설정돼요.

초기화식 (Initializers)

[Initializer]():
    [*VoidInitializer*](#VoidInitializer)
    [*NonVoidInitializer*](#NonVoidInitializer)

[NonVoidInitializer]():
    [*ArrayInitializer*](../spec/arrays.html#ArrayInitializer)[](#)
    [*StructInitializer*](../spec/struct.html#StructInitializer)[](#)
    [*AssignExpression*](../spec/expression.html#AssignExpression)[](#)

변수는 NonVoidInitializer 로 초기화할 수 있어요. ArrayInitializer 또는 StructInitializer 는 표현식 초기화식보다 우선해요.

struct S { int i; }

S s = {}; // struct initializer, not a function literal expression
S[] a = [{2}]; // array initializer holding a struct initializer
//a = [{2}]; // invalid array literal expression

Void 초기화 (Void Initialization)

[VoidInitializer]():
    void

보통 변수는 초기화돼요. 그러나 변수 초기화식이 void이면 변수는 초기화되지 않아요. 안전하지 않은 값(포인터가 있는 타입 같은)을 포함할 수 있는 타입을 가진 변수에 대한 void 초기화식은 @safe 코드에서 허용되지 않아요.

구현 정의(Implementation Defined): void 초기화 변수의 값이 설정되기 전에 사용되면, 그 값은 구현 정의예요.

void bad()
{
    int x = void;
    writeln(x);  // print implementation defined value
}

정의되지 않은 동작(Undefined Behavior): void 초기화 변수의 값이 설정되기 전에 사용되고, 그 값이 참조, 포인터 또는 불변 조건(invariant)이 있는 struct 인스턴스라면, 동작은 정의되지 않아요.

void muchWorse()
{
    char[] p = void;
    writeln(p);  // may result in apocalypse
}

모범 사례(Best Practices):

  • Void 초기화식은 임시 버퍼처럼 정적 배열이 스택에 있지만 부분적으로만 사용될 수 있을 때 유용해요. Void 초기화식은 잠재적으로 코드를 빠르게 만들지만, 배열 요소가 읽기 전에 항상 설정되도록 보장해야 하므로 위험을 도입해요.
  • struct에 대해서도 마찬가지예요.
  • void 초기화식의 사용은 개별 로컬 변수에는 거의 유용하지 않아요. 현대 최적화기는 변수가 나중에 초기화되면 초기화의 죽은 저장(dead store)을 제거하기 때문이에요.
  • 핫 코드 경로에서는 void 초기화식이 실제로 결과를 개선하는지 프로파일링해 볼 가치가 있어요.

암시적 타입 추론 (Implicit Type Inference)

[AutoDeclaration]():
    [*StorageClasses*](#StorageClasses) [*AutoAssignments*](#AutoAssignments) ;

[AutoAssignments]():
    [*AutoAssignment*](#AutoAssignment)
    *AutoAssignments* , [*AutoAssignment*](#AutoAssignment)

[AutoAssignment]():
    [*Identifier*](../spec/lex.html#Identifier) [*TemplateParameters*](../spec/template.html#TemplateParameters)opt = [*Initializer*](#Initializer)
    [*TuplePattern*](#TuplePattern) = [*AssignExpression*](../spec/expression.html#AssignExpression)

선언이 StorageClass 로 시작하고 타입을 추론할 수 있는 NonVoidInitializer 를 가지면, 선언의 타입을 생략할 수 있어요.

static x = 3;      // x is type int
auto y = 4u;       // y is type uint

auto s = "Apollo"; // s is type immutable(char)[] i.e., string

class C { ... }

auto c = new C();  // c is a handle to an instance of class C

NonVoidInitializer 는 앞 참조(forward reference)를 포함할 수 없어요 (이 제한은 향후 제거될 수 있어요). 암시적으로 추론된 타입은 런타임이 아니라 컴파일 타임에 선언에 정적으로 바인딩돼요.

ArrayLiteral은 정적 배열 타입이 아니라 동적 배열 타입으로 추론돼요.

auto v = ["resistance", "is", "useless"]; // type is string[], not string[3]

언패킹 선언 (Unpacking Declarations)

VarDeclarationsAutoDeclarationAssignExpression에서 튜플 패턴(tuple pattern) 으로 하나 이상의 변수를 초기화하는 것을 지원해요. 그 AssignExpression값 시퀀스로 암시적으로 변환되어야 해요.

[TuplePattern]():
    ( [*TuplePatternComponents*](#TuplePatternComponents) )

[TuplePatternComponents]():
    [*TuplePatternComponent*](#TuplePatternComponent) ,
    [*TuplePatternComponent*](#TuplePatternComponent) , [*TuplePatternComponent*](#TuplePatternComponent)
    [*TuplePatternComponent*](#TuplePatternComponent) , [*TuplePatternComponents*](#TuplePatternComponents)

[TuplePatternComponent]():
    [*StorageClasses*](#StorageClasses)opt [*Identifier*](../spec/lex.html#Identifier)
    [*StorageClasses*](#StorageClasses)opt [*BasicType*](../spec/type.html#BasicType) [*TypeSuffixes*](../spec/type.html#TypeSuffixes)opt [*Identifier*](../spec/lex.html#Identifier)
    [*StorageClasses*](#StorageClasses)opt [*TuplePattern*](#TuplePattern)

  • 튜플 패턴의 각 구성 요소는 값 시퀀스의 한 요소에 대응해요.
  • 구성 요소는 쉼표로 구분돼요.
  • 구성 요소가 하나인 패턴의 경우 닫는 괄호 앞에 쉼표가 있어야 해요.
import std.typecons : tuple;

(int a, auto b) = tuple(1, "2"); // unpacking declaration
assert(a == 1);
assert(b == "2");

위의 언패킹 선언은 예를 들어 다음으로 낮춰져요(lowered).

auto temp = tuple(1, "2");
int a = temp[0];
auto b = temp[1];

참고: std.typecons.Tuple.

튜플 패턴에 저장 클래스가 없으면, 변수를 선언하는 각 구성 요소는 타입이나 적용되는 저장 클래스를 가져야 해요.

(int a, b) = tuple(1, "2"); // Error: `b` has no type or storage class

패턴에 저장 클래스가 있으면 패턴의 각 구성 요소는 식별자만 될 수 있어요. 각 변수의 타입은 대응하는 시퀀스 요소에서 추론돼요.

import std.typecons : tuple;

auto (a, b) = tuple(1, "2");
static assert(is(typeof(a) == int));
static assert(is(typeof(b) == string));

auto (a, immutable b, c) = tuple(1, "2", 3.0);
static assert(is(typeof(a) == int));
static assert(is(typeof(b) == immutable string));
static assert(is(typeof(c) == double));

패턴은 중첩될 수 있어요.

auto t = tuple(1, tuple("2", 3.0));
// the following are equivalent:
auto (a, (b, c)) = t;
(int a, (string b, double c)) = t;
(int a, auto (b, c)) = t;
(int a, (auto b, auto c)) = t;

static 또는 enum은 전체 선언에 적용할 수 있지만 개별 구성 요소에는 적용할 수 없어요.

전역 및 static 초기화식 (Global and Static Initializers)

전역 또는 static 변수의 Initializer는 컴파일 타임에 평가 가능해야 해요. 런타임 초기화는 static 생성자로 수행돼요.

구현 정의(Implementation Defined): 일부 포인터를 다른 함수나 데이터의 주소로 초기화할 수 있는지 여부.

정적으로 할당된 데이터 (Statically Allocated Data)

함수나 집계 타입 밖의 VarDeclarations는 정적으로 할당돼요. 기본적으로 그러한 변수는 다음 StorageClasses 중 하나를 가지지 않는 한 스레드 로컬이에요.

기본적으로 함수 안의 VarDeclarations 는 스택에 할당돼요. 다음 StorageClasses 는 대신 데이터를 정적으로 할당하며, 함수 안에서조차 또는 집계 타입 안에서 그렇지 않을 수도 있어요.

  • static — 스레드 로컬 데이터 할당
  • shared static 또는 __gshared — 전역 데이터 할당

참고: StorageClassesenum 또는 extern이 포함되면, 변수 선언은 저장 공간을 할당받지 않아요.

구현 정의(Implementation Defined): 초기화식이 void이거나 모든 비트가 0인 static 선언은 저장 공간을 절약하기 위해 바이너리의 .bss 섹션에 나열될 수 있어요.

alias 선언 (Alias Declarations)

2024 Edition: AliasAssignments 형식만 사용하세요 — legacy를 참고하세요.

[AliasDeclaration]():
    alias [*StorageClasses*](#StorageClasses)opt [*BasicType*](../spec/type.html#BasicType) [*TypeSuffixes*](../spec/type.html#TypeSuffixes)opt [*Identifiers*](#Identifiers) ;
    alias [*StorageClasses*](#StorageClasses)opt [*BasicType*](../spec/type.html#BasicType) [*FuncDeclarator*](../spec/function.html#FuncDeclarator) ;
    alias [*AliasAssignments*](#AliasAssignments) ;

[Identifiers]():
    [*Identifier*](../spec/lex.html#Identifier)
    [*Identifier*](../spec/lex.html#Identifier) , *Identifiers*

[AliasAssignments]():
    [*AliasAssignment*](#AliasAssignment)
    *AliasAssignments* , [*AliasAssignment*](#AliasAssignment)

[AliasAssignment]():
    [*Identifier*](../spec/lex.html#Identifier) [*TemplateParameters*](../spec/template.html#TemplateParameters)opt = [*StorageClasses*](#StorageClasses)opt [*Type*](../spec/type.html#Type)
    [*Identifier*](../spec/lex.html#Identifier) [*TemplateParameters*](../spec/template.html#TemplateParameters)opt = [*FunctionLiteral*](../spec/expression.html#FunctionLiteral)
    [*Identifier*](../spec/lex.html#Identifier) [*TemplateParameters*](../spec/template.html#TemplateParameters)opt = [*StorageClasses*](#StorageClasses)opt [*Type*](../spec/type.html#Type) [*Parameters*](../spec/function.html#Parameters) [*MemberFunctionAttributes*](../spec/function.html#MemberFunctionAttributes)opt

AliasDeclaration 은 타입이나 다른 심볼을 가리키는 심볼 이름을 만들어요. 그 이름은 대상이 나타날 수 있는 어디에서든 사용할 수 있어요. 다음을 별칭(alias)으로 만들 수 있어요.

타입 별칭 (Type Aliases)

alias myint = abc.Foo.bar;

별칭된 타입은 별칭 대상 타입과 의미론적으로 동일해요. 디버거는 그것들을 구별할 수 없고, 함수 오버로딩의 관점에서도 차이가 없어요. 예를 들어:

alias myint = int;

void foo(int x) { ... }
void foo(myint m) { ... } // error, multiply defined function foo

타입 별칭은 때때로 다른 심볼 별칭과 구별할 수 없어 보일 수 있어요.

alias abc = foo.bar; // is it a type or a symbol?

모범 사례: 기본 타입 이름의 별칭을 만드는 경우 외에는, 타입 별칭 이름을 대문자로 시작해야 해요.

심볼 별칭 (Symbol Aliases)

심볼은 다른 심볼의 alias 로 선언할 수 있어요. 예를 들어:

import planets;

alias myAlbedo = planets.albedo;
...
int len = myAlbedo("Saturn"); // actually calls planets.albedo()

다음 alias 선언은 유효해요.

template Foo2(T) { alias t = T; }
alias t1 = Foo2!(int);
alias t2 = Foo2!(int).t;
alias t3 = t1.t;
alias t4 = t2;

t1.t v1;  // v1 is type int
t2 v2;    // v2 is type int
t3 v3;    // v3 is type int
t4 v4;    // v4 is type int

별칭된 심볼은 긴 한정 심볼 이름의 약어로, 또는 참조를 한 심볼에서 다른 심볼로 리다이렉트하는 방법으로 유용해요.

version (Win32)
{
    alias myfoo = win32.foo;
}
version (linux)
{
    alias myfoo = linux.bar;
}

별칭은 import된 모듈이나 패키지에서 현재 스코프로 심볼을 'import'하는 데 사용할 수 있어요.

static import string;
...
alias strlen = string.strlen;

오버로드 집합 별칭 (Aliasing an Overload Set)

별칭은 현재 스코프의 함수로 오버로드할 수 있는 오버로드된 함수 집합도 'import'할 수 있어요.

class B
{
    int foo(int a, uint b) { return 2; }
}

class C : B
{
    // declaring an overload hides any base class overloads
    int foo(int a) { return 3; }
    // redeclare hidden overload
    alias foo = B.foo;
}

void main()
{
    import std.stdio;

    C c = new C();
    c.foo(1, 2u).writeln;   // calls B.foo
    c.foo(1).writeln;       // calls C.foo
}

변수 별칭 (Aliasing Variables)

변수는 별칭할 수 있지만 표현식은 별칭할 수 없어요.

int i = 0;
alias a = i; // OK
alias b = a; // alias a variable alias
a++;
b++;
assert(i == 2);

//alias c = i * 2; // error
//alias d = i + i; // error

집계의 멤버는 별칭할 수 있지만, non-static 필드 별칭은 부모 타입 밖에서 접근할 수 없어요.

struct S
{
    static int i = 0;
    int j;
    alias a = j; // OK

    void inc() { a++; }
}

alias a = S.i; // OK
a++;
assert(S.i == 1);

alias b = S.j; // allowed
static assert(b.offsetof == 0);
//b++;   // error, no instance of S
//S.a++; // error, no instance of S

S s = S(5);
s.inc();
assert(s.j == 6);
//alias c = s.j; // scheduled for deprecation

함수 타입 별칭 (Aliasing a Function Type)

함수 타입은 별칭할 수 있어요.

alias Fun = int(string);
int fun(string) {return 0;}
static assert(is(typeof(fun) == Fun));

alias MemberFun1 = int() const;
alias MemberFun2 = const int();
// leading attributes apply to the func, not the return type
static assert(is(MemberFun1 == MemberFun2));

타입 별칭은 다른 기본 인자로 함수를 호출하거나, 인자를 필수에서 기본으로 또는 그 반대로 변경하는 데 사용할 수 있어요.

import std.stdio : writeln;

void fun(int v = 6)
{
    writeln("v: ", v);
}

void main()
{
    fun();  // prints v: 6

    alias Foo = void function(int=7);
    Foo foo = &fun;
    foo();  // prints v: 7
    foo(8); // prints v: 8
}

import std.stdio : writefln;

void main()
{
    fun(4);          // prints a: 4, b: 6, c: 7

    Bar bar = &fun;
    //bar(4);           // compilation error, because the `Bar` alias
                        // requires an explicit 2nd argument
    bar(4, 5);          // prints a: 4, b: 5, c: 9
    bar(4, 5, 6);       // prints a: 4, b: 5, c: 6

    Baz baz = &fun;
    baz();              // prints a: 2, b: 3, c: 4
}

alias Bar = void function(int, int, int=9);
alias Baz = void function(int=2, int=3, int=4);

void fun(int a, int b = 6, int c = 7)
{
    writefln("a: %d, b: %d, c: %d", a, b, c);
}

Alias Assign

[AliasAssign]():
    [*Identifier*](../spec/lex.html#Identifier) = [*Type*](../spec/type.html#Type)

AliasDeclarationAliasAssign 으로 새 값을 할당받을 수 있어요.

template Gorgon(T)
{
    alias A = long;
    A = T; // assign new value to A
    alias Gorgon = A;
}
pragma(msg, Gorgon!int); // prints int

  • AliasAssign 과 그 대응 AliasDeclaration 은 둘 다 같은 TemplateDeclaration에 선언되어야 해요.
  • 대응 AliasDeclarationAliasAssign 보다 어휘적으로 앞에 나타나야 해요.
  • 대응 AliasDeclaration 은 오버로드된 심볼을 참조할 수 없어요.
  • AliasDeclaration 또는 AliasAssign 의 왼쪽(lvalue) 값은, 그 AliasAssign 의 오른쪽이 아닌 다른 AliasAssign 이 같은 lvalue에 적용되기 전에는 사용되지 못할 수 있어요.

모범 사례: AliasAssign 은 재귀 계산보다 반복 계산을 사용할 때 특히 유용해요. 재귀 방식이 만들어내는 많은 수의 중간 템플릿을 피하기 때문이에요.

import std.meta : AliasSeq;

static if (0) // recursive method for comparison
{
    template Reverse(T...)
    {
        static if (T.length == 0)
            alias Reverse = AliasSeq!();
        else
            alias Reverse = AliasSeq!(Reverse!(T[1 .. T.length]), T[0]);
    }
}
else // iterative method minimizes template instantiations
{
    template Reverse(T...)
    {
        alias A = AliasSeq!();
        static foreach (t; T)
            A = AliasSeq!(t, A); // Alias Assign
        alias Reverse = A;
    }
}

enum X = 3;
alias TK = Reverse!(int, const uint, X);
pragma(msg, TK); // prints tuple(3, (const(uint)), (int))

Alias 재할당 (Alias Reassignment)

[AliasReassignment]():
    [*Identifier*](../spec/lex.html#Identifier) = [*StorageClasses*](#StorageClasses)opt [*Type*](../spec/type.html#Type)
    [*Identifier*](../spec/lex.html#Identifier) = [*FunctionLiteral*](../spec/expression.html#FunctionLiteral)
    [*Identifier*](../spec/lex.html#Identifier) = [*StorageClasses*](#StorageClasses)opt [*BasicType*](../spec/type.html#BasicType) [*Parameters*](../spec/function.html#Parameters) [*MemberFunctionAttributes*](../spec/function.html#MemberFunctionAttributes)opt

템플릿 안의 alias 선언은 새 값으로 재할당될 수 있어요.

import std.meta : AliasSeq;

template staticMap(alias F, Args...)
{
    alias A = AliasSeq!();
    static foreach (Arg; Args)
        A = AliasSeq!(A, F!Arg); // alias reassignment
    alias staticMap = A;
}

enum size(T) = T.sizeof;
static assert(staticMap!(size, char, wchar, dchar) == AliasSeq!(1, 2, 4));

Identifier 는 어휘적으로 앞서는 AliasDeclaration으로 해석되어야 해요. 둘 다 같은 TemplateDeclaration의 멤버여야 해요.

AliasReassignment 의 오른쪽은 AliasDeclaration 의 오른쪽을 대체해요.

AliasDeclarationAliasReassignment 의 오른쪽이 아닌 어떤 컨텍스트에서든 한 번 참조되면 더 이상 재할당할 수 없어요.

이론적 근거: Alias 재할당은 컴파일 시간을 단축하고 메모리 소비를 낮출 수 있으며, 대안인 재귀 방식보다 훨씬 간단한 코드를 요구해요.

extern 선언 (Extern Declarations)

저장 클래스 extern을 가진 변수 선언은 모듈 내에서 저장 공간을 할당받지 않아요. 그것들은 일치하는 이름을 가진 다른 객체 파일에 정의되어 있어야 하며, 그 파일이 링크돼요.

extern 선언은 선택적으로 extern 링키지 속성이 뒤따를 수 있어요. 링키지 속성이 없으면 extern(D)로 기본 설정돼요.

// variable allocated and initialized in this module with C linkage
extern(C) int foo;

// variable allocated outside this module with C linkage
// (e.g. in a statically linked C library or another module)
extern extern(C) int bar;

모범 사례: Extern Declarations 의 주된 유용성은 C나 C++ 파일의 전역 변수 선언과 함수에 연결하는 것이에요.

타입 한정자 vs 저장 클래스 (Type Qualifiers vs. Storage Classes)

타입 한정자저장 클래스는 구별되는 개념이에요.

타입 한정자 는 기존 기본 타입에서 파생 타입을 만들며, 결과 타입은 그 타입의 여러 인스턴스를 만드는 데 사용될 수 있어요.

예를 들어 immutable 타입 한정자는 immutable 타입의 변수를 만드는 데 사용할 수 있어요. 예를 들면:

immutable(int)   x; // typeof(x) == immutable(int)
immutable(int)[] y; // typeof(y) == immutable(int)[]
                    // typeof(y[0]) == immutable(int)

// Type constructors create new types that can be aliased:
alias ImmutableInt = immutable(int);
ImmutableInt z;     // typeof(z) == immutable(int)

반면 저장 클래스 는 새 타입을 만들지 않고, 선언되는 변수나 함수가 사용하는 저장 공간의 종류만 설명해요. 예를 들어 멤버 함수는 const 저장 클래스로 선언해 암시적 this 인자를 수정하지 않음을 나타낼 수 있어요.

struct S
{
    int x;
    int method() const
    {
        //x++;    // Error: this method is const and cannot modify this.x
        return x; // OK: we can still read this.x
    }
}

일부 키워드는 타입 한정자와 저장 클래스 모두로 사용될 수 있지만, ref처럼 새 타입을 구성하는 데 사용할 수 없는 저장 클래스도 있어요.

ref 저장 클래스 (ref Storage Class)

ref로 선언된 매개변수는 참조로 전달돼요.

void func(ref int i)
{
    i++; // modifications to i will be visible in the caller
}

void main()
{
    auto x = 1;
    func(x);
    assert(x == 2);

    // However, ref is not a type qualifier, so the following is illegal:
    //ref(int) y; // Error: ref is not a type qualifier.
}

함수는 ref로 선언될 수도 있으며, 이는 반환 값이 참조로 전달된다는 뜻이에요.

ref int func2()
{
    static int y = 0;
    return y;
}

void main()
{
    func2() = 2; // The return value of func2() can be modified.
    assert(func2() == 2);

    // However, the reference returned by func2() does not propagate to
    // variables, because the 'ref' only applies to the return value itself,
    // not to any subsequent variable created from it:
    auto x = func2();
    static assert(is(typeof(x) == int)); // N.B.: *not* ref(int);
                                     // there is no such type as ref(int).
    x++;
    assert(x == 3);
    assert(func2() == 2); // x is not a reference to what func2() returned; it
                          // does not inherit the ref storage class from func2().
}

ref 변수 (ref Variables)

버전 2.111부터 ref는 로컬, static, extern, 전역 변수를 선언하는 데 사용할 수 있어요.

struct S { int a; }

void main()
{
    S s;
    ref int r = s.a;
    r = 3;
    assert(s.a == 3);
}

모범 사례: 포인터 산술이 필요하지 않을 때 포인터 대신 ref 변수를 사용하세요.

auto ref는 로컬, static, extern, 전역 변수를 선언하는 데에도 사용할 수 있어요.

void f()
{
    auto ref x = 0;
    auto ref y = x;
    static assert(!__traits(isRef, x));
    static assert( __traits(isRef, y));
}

참고: auto ref 템플릿 함수 매개변수.

한정된 타입을 반환하는 메서드 (Methods Returning a Qualified Type)

const 같은 일부 키워드는 타입 한정자와 저장 클래스 모두로 사용될 수 있어요. 구별은 그것이 나타나는 문법에 의해 결정돼요.

struct S
{
    /* Is const here a type qualifier or a storage class?
     * Is the return value const(int), or is this a const function that returns
     * (mutable) int?
     */
    const int* func() // a const function
    {
        //++p;          // error, this.p is const
        //return p;     // error, cannot convert const(int)* to int*
        return null;
    }

    const(int)* func() // a function returning a pointer to a const int
    {
        ++p;          // ok, this.p is mutable
        return p;     // ok, int* can be implicitly converted to const(int)*
    }

    int* p;
}

모범 사례: 혼동을 피하기 위해 반환 타입에는 괄호가 있는 타입 한정자 문법을 사용하고, 함수 저장 클래스는 반환 타입과 시각적으로 혼동될 수 있는 왼쪽이 아니라 선언의 오른쪽에 작성하세요.

struct S
{
    // Now it is clear that the 'const' here applies to the return type:
    const(int) func1() { return 1; }

    // And it is clear that the 'const' here applies to the function:
    int func2() const { return 1; }
}

더 알아보기