조건부 컴파일
조건부 컴파일 (Conditional Compilation)
조건부 컴파일은 어떤 코드를 컴파일하고 어떤 코드를 컴파일하지 않을지를 선택하는 과정이에요. D는 이를 위해 version, debug, static if, static foreach, static assert와 같은 조건부 구성을 제공해요. 이 문서는 이 조건부 컴파일 메커니즘들과 사전 정의된 버전 식별자들을 다루어요.
본문
조건부 컴파일은 어떤 코드를 컴파일하고 어떤 코드를 컴파일하지 않을지를 선택하는 과정이에요.
ConditionalDeclaration:
Condition DeclarationBlock
Condition DeclarationBlock else DeclarationBlock
Condition : DeclDefsopt
Condition DeclarationBlock else : DeclDefsopt
ConditionalStatement:
Condition NoScopeNonEmptyStatement
Condition NoScopeNonEmptyStatement else NoScopeNonEmptyStatement
Condition이 충족되면 뒤따르는 DeclarationBlock 또는 Statement가 컴파일되고, 충족되지 않으면 선택적 else 뒤의 DeclarationBlock 또는 Statement가 컴파일돼요. 컴파일되지 않는 DeclarationBlock이나 Statement도 여전히 문법적으로 올바르게(syntactically correct) 유지되어야 해요.
{ }로 둘러싸여 있어도 새로운 스코프는 도입되지 않아요. ConditionalDeclaration과 ConditionalStatement는 중첩될 수 있어요. StaticAssert는 조건부 컴파일 분기 중 오류인 것들에 대해 컴파일 타임 오류를 발생시키는 데 사용할 수 있어요.
Condition은 다음 형태를 가져요:
Condition:
VersionCondition
DebugCondition
StaticIfCondition
Version 조건 (Version Condition)
VersionCondition:
version ( Identifier )
version ( unittest )
version ( assert )
Version은 단일 소스 파일로 모듈의 여러 버전을 구현할 수 있게 해줘요.
VersionCondition은 Identifier가 버전 식별자(version identifier)와 일치하면 충족돼요. 버전 식별자는 명령줄의 -version 스위치나 모듈 안의 VersionSpecification으로 설정될 수 있고, 컴파일러가 사전 정의할 수도 있어요.
버전 식별자들은 자신만의 고유 이름 공간(name space)을 가지며, debug 식별자나 모듈의 다른 심볼들과 충돌하지 않아요. 한 모듈에서 정의된 버전 식별자는 다른 임포트된 모듈에는 영향을 미치지 않아요.
int k;
version (Demo) // compile in this code block for the demo version
{
int i;
int k; // error, k already defined
i = 3;
}
x = i; // uses the i declared above
version (X86)
{
... // implement custom inline assembler version
}
else
{
... // use default, but slow, version
}
version (unittest)는 코드가 단위 테스트를 활성화한 상태로 컴파일될 때만(dmd의 -unittest 옵션) 충족돼요.
Version 명세 (Version Specification)
VersionSpecification:
version = Identifier ;
version 명세는 특정 버전 식별자를 정의해요. 이는 기능 집합을 하나의 주요 버전 아래로 묶는 것을 간단하게 만들어요. 예를 들어:
version (ProfessionalEdition)
{
version = FeatureA;
version = FeatureB;
version = FeatureC;
}
version (HomeEdition)
{
version = FeatureA;
}
...
version (FeatureB)
{
... implement Feature B ...
}
버전 식별자는 전방 참조(forward reference)될 수 없어요:
version (Foo)
{
int x;
}
version = Foo; // error, Foo already used
VersionSpecification은 모듈 스코프에만 나타날 수 있고, MixinExpression으로 생성될 수 없어요.
debug와 version 조건은 겉보기에는 똑같이 동작하지만, 아주 다른 목적을 위해 의도됐어요. Debug 문은 릴리스 버전에서 제거될 디버그 코드를 추가하기 위한 것이에요. Version 문은 이식성(portability)과 여러 릴리스 버전을 돕기 위한 것이에요.
다음은 데모 버전과 대비되는 전체(full) 버전의 예시예요:
class Foo
{
int a, b;
version (full)
{
int extrafunctionality()
{
...
return 1; // extra functionality is supported
}
}
else // demo
{
int extrafunctionality()
{
return 0; // extra functionality is not supported
}
}
}
version에 매개변수를 주어 다양한 버전 빌드를 만들 수 있어요:
version (identifier) // add in version code if version
// keyword is identifier
{
... version code ...
}
이는 명령줄에서 -version=identifier로 설정된다고 여겨져요.
사전 정의된 버전 (Predefined Versions)
일관된 사용을 위해 여러 환경 버전 식별자와 식별자 이름 공간이 사전 정의돼 있어요. 버전 식별자는 코드의 다른 식별자들과 충돌하지 않아요. 별도의 이름 공간에 있기 때문이에요. 사전 정의된 버전 식별자는 전역적이에요. 즉, 컴파일되고 임포트되는 모든 모듈에 적용돼요.
호스트 컴파일러 (Host Compiler):
| 버전 식별자 | 설명 |
|---|---|
DigitalMars |
DMD (Digital Mars D) |
GNU |
GDC (GNU D Compiler) |
LDC |
LDC (LLVM D Compiler) |
SDC |
SDC (Snazzy D Compiler) |
대상 운영 체제 (Target Operating System):
| 버전 식별자 | 설명 |
|---|---|
Windows |
Microsoft Windows 시스템 (Win32 및 Win64) |
Win32 |
Microsoft 32비트 Windows 시스템 |
Win64 |
Microsoft 64비트 Windows 시스템 |
linux |
모든 Linux 시스템 |
Apple |
Apple 시스템 (OSX, iOS, TVOS, WatchOS, VisionOS) |
OSX |
macOS |
iOS |
iOS |
TVOS |
tvOS |
WatchOS |
watchOS |
VisionOS |
visionOS |
FreeBSD |
FreeBSD |
OpenBSD |
OpenBSD |
NetBSD |
NetBSD |
DragonFlyBSD |
DragonFlyBSD |
BSD |
그 외 모든 BSD |
Solaris |
Solaris |
Posix |
모든 POSIX 시스템 (Linux, FreeBSD, OS X, Solaris 등 포함) |
AIX |
IBM Advanced Interactive eXecutive OS |
Haiku |
Haiku 운영 체제 |
SkyOS |
SkyOS 운영 체제 |
SysV3 |
System V Release 3 |
SysV4 |
System V Release 4 |
Hurd |
GNU Hurd |
Android |
Android 플랫폼 |
Emscripten |
Emscripten 플랫폼 |
PlayStation |
PlayStation 플랫폼 |
PlayStation4 |
PlayStation 4 플랫폼 |
WASI |
WebAssembly System Interface의 모든 버전 |
WASIp1 |
WebAssembly System Interface Preview 1 |
WASIp2 |
WebAssembly System Interface Preview 2 |
WASIp3 |
WebAssembly System Interface Preview 3 |
FreeStanding |
운영 체제가 없는 환경 (예: 베어메탈 대상) |
대상 환경 (Target Environment):
| 버전 식별자 | 설명 |
|---|---|
Cygwin |
Cygwin 환경 |
MinGW |
MinGW 환경 |
CRuntime_Bionic |
Bionic C 런타임 |
CRuntime_DigitalMars |
DigitalMars C 런타임 |
CRuntime_Glibc |
Glibc C 런타임 |
CRuntime_Microsoft |
Microsoft C 런타임 |
CRuntime_Musl |
musl C 런타임 |
CRuntime_Newlib |
newlib C 런타임 |
CRuntime_UClibc |
uClibc C 런타임 |
CRuntime_WASI |
WASI C 런타임 |
CppRuntime_Clang |
(deprecated) 대신 CppRuntime_LLVM 사용 |
CppRuntime_DigitalMars |
DigitalMars C++ 런타임 |
CppRuntime_Gcc |
(deprecated) 대신 CppRuntime_GNU 사용 |
CppRuntime_LLVM |
LLVM libc++ C++ 런타임 |
CppRuntime_GNU |
GNU libstdc++ C++ 런타임 |
CppRuntime_Microsoft |
Microsoft C++ 런타임 |
CppRuntime_Sun |
Sun C++ 런타임 |
대상 CPU 및 명령어 집합 (Target CPU and Instruction Set):
| 버전 식별자 | 설명 |
|---|---|
X86 |
Intel 및 AMD 32비트 프로세서 |
X86_64 |
Intel 및 AMD 64비트 프로세서 |
ARM |
ARM 아키텍처 (32비트) (AArch32 등) |
ARM_Thumb |
Thumb 모드의 ARM |
ARM_SoftFloat |
ARM 소프트 부동소수점 ABI |
ARM_SoftFP |
ARM softfp 부동소수점 ABI |
ARM_HardFloat |
ARM hardfp 부동소수점 ABI |
AArch64 |
Advanced RISC Machine 아키텍처 (64비트) |
AsmJS |
asm.js 중간 프로그래밍 언어 |
AVR |
8비트 Atmel AVR 마이크로컨트롤러 |
Epiphany |
Epiphany 아키텍처 |
PPC |
PowerPC 아키텍처, 32비트 |
PPC_SoftFloat |
PowerPC 소프트 float ABI |
PPC_HardFloat |
PowerPC 하드 float ABI |
PPC64 |
PowerPC 아키텍처, 64비트 |
IA64 |
Itanium 아키텍처 (64비트) |
MIPS32 |
MIPS 아키텍처, 32비트 |
MIPS64 |
MIPS 아키텍처, 64비트 |
MIPS_O32 |
MIPS O32 ABI |
MIPS_N32 |
MIPS N32 ABI |
MIPS_O64 |
MIPS O64 ABI |
MIPS_N64 |
MIPS N64 ABI |
MIPS_EABI |
MIPS EABI |
MIPS_SoftFloat |
MIPS soft-float ABI |
MIPS_HardFloat |
MIPS hard-float ABI |
MSP430 |
MSP430 아키텍처 |
NVPTX |
Nvidia Parallel Thread Execution (PTX) 아키텍처, 32비트 |
NVPTX64 |
Nvidia Parallel Thread Execution (PTX) 아키텍처, 64비트 |
RISCV32 |
RISC-V 아키텍처, 32비트 |
RISCV64 |
RISC-V 아키텍처, 64비트 |
SPARC |
SPARC 아키텍처, 32비트 |
SPARC_V8Plus |
SPARC v8+ ABI |
SPARC_SoftFloat |
SPARC 소프트 float ABI |
SPARC_HardFloat |
SPARC 하드 float ABI |
SPARC64 |
SPARC 아키텍처, 64비트 |
S390 |
System/390 아키텍처, 32비트 |
SystemZ |
System Z 아키텍처, 64비트 |
HPPA |
HP PA-RISC 아키텍처, 32비트 |
HPPA64 |
HP PA-RISC 아키텍처, 64비트 |
SH |
SuperH 아키텍처, 32비트 |
WebAssembly |
WebAssembly 가상 ISA (명령어 집합 아키텍처), 32비트 |
Xtensa |
Xtensa 아키텍처, 32비트 |
Alpha |
Alpha 아키텍처 |
Alpha_SoftFloat |
Alpha 소프트 float ABI |
Alpha_HardFloat |
Alpha 하드 float ABI |
바이트 순서 (Byte Order, endianness):
| 버전 식별자 | 설명 |
|---|---|
LittleEndian |
바이트 순서, 최하위 바이트가 먼저 |
BigEndian |
바이트 순서, 최상위 바이트가 먼저 |
실행 및 링크 형식 (Executable and Link Format):
| 버전 식별자 | 설명 |
|---|---|
ELFv1 |
Elf 버전 1 |
ELFv2 |
Elf 버전 2 |
기타 (Miscellaneous):
| 버전 식별자 | 설명 |
|---|---|
D_BetterC |
D as Better C 코드(명령줄 스위치 -betterC)가 생성되고 있음 |
D_Exceptions |
예외 처리가 지원됨. -betterC로 컴파일할 때 false로 평가됨 |
D_ModuleInfo |
ModuleInfo가 지원됨. -betterC로 컴파일할 때 false로 평가됨 |
D_TypeInfo |
런타임 타입 정보(a.k.a TypeInfo)가 지원됨. -betterC로 컴파일할 때 false로 평가됨 |
D_Coverage |
코드 커버리지 분석 계측(명령줄 스위치 -cov)이 생성되고 있음 |
D_Ddoc |
Ddoc 문서(명령줄 스위치 -D)가 생성되고 있음 |
D_InlineAsm_X86 |
X86용 인라인 어셈블러가 구현됨 |
D_InlineAsm_X86_64 |
X86-64용 인라인 어셈블러가 구현됨 |
D_LP64 |
포인터가 64비트(명령줄 스위치 -m64). (이것을 C의 LP64 모델과 혼동하지 마세요) |
D_X32 |
포인터는 32비트지만 워드는 여전히 64비트 (x32 ABI) (X86_64와 병렬로 정의될 수 있음) |
D_HardFloat |
대상 하드웨어에 부동소수점 유닛이 있음 |
D_SoftFloat |
대상 하드웨어에 부동소수점 유닛이 없음 |
D_PIC |
Position Independent Code(명령줄 스위치 -fPIC)가 생성되고 있음 |
D_PIE |
Position Independent Executable(명령줄 스위치 -fPIE)가 생성되고 있음 |
D_SIMD |
벡터 확장(__simd 경유)이 지원됨 |
D_AVX |
AVX 벡터 명령어가 지원됨 |
D_AVX2 |
AVX2 벡터 명령어가 지원됨 |
D_Version2 |
D 버전 2 컴파일러임 |
D_NoBoundsChecks |
배열 경계 검사가 비활성화됨(명령줄 스위치 -boundscheck=off) |
D_ObjectiveC |
대상이 Objective-C와의 인터페이싱을 지원함 |
D_ProfileGC |
GC 할당이 프로파일링되고 있음(명령줄 스위치 -profile=gc) |
D_Optimized |
최적화가 활성화된 상태로 컴파일 중(명령줄 스위치 -O) |
Core |
표준 런타임을 빌드할 때 정의됨 |
Std |
표준 라이브러리를 빌드할 때 정의됨 |
unittest |
단위 테스트가 활성화됨(명령줄 스위치 -unittest) |
assert |
AssertExpression에 대한 검사가 방출되고 있음 |
D_PreConditions |
in 계약(contract)에 대한 검사가 방출되고 있음 |
D_PostConditions |
out 계약에 대한 검사가 방출되고 있음 |
D_Invariants |
클래스 불변식과 struct 불변식에 대한 검사가 방출되고 있음 |
특수 경우 (Special Cases):
| 버전 식별자 | 설명 |
|---|---|
none |
절대 정의되지 않음; 코드 섹션을 비활성화하는 데 사용됨 |
all |
항상 정의됨; none의 반대로 사용됨 |
다음 식별자들은 정의되지만 더 이상 사용되지 않아요(deprecated):
| 버전 식별자 | 설명 |
|---|---|
darwin |
Darwin 운영 체제; 대신 OSX 사용 |
Thumb |
Thumb 모드의 ARM; 대신 ARM_Thumb 사용 |
S390X |
System/390X 아키텍처, 64비트; 대신 SystemZ 사용 |
그 외 것들은 타당해지고 새로운 구현이 나타남에 따라 추가될 거예요. 언어의 미래 성장을 허용하기 위해, "D_"로 시작하는 버전 식별자 이름 공간은 D 언어 명세 또는 새 기능 적합성을 나타내는 식별자들을 위해 예약돼 있어요. 나아가 위에 나열된 것들에 어떤 문자든 붙여서 파생된 모든 식별자는 예약돼요. 이는 예를 들어 ARM_foo와 Windows_bar는 예약된 반면 foo_ARM과 bar_Windows는 예약되지 않음을 의미해요.
이 목록의 사전 정의된 버전 식별자들은 명령줄이나 version 문에서 설정될 수 없어요. (이는 Windows와 linux가 동시에 설정되는 것 같은 일을 방지해요.)
컴파일러 벤더 고유 버전은 상표화된 벤더 식별자가 접두사로 붙으면 사전 정의될 수 있어요. 예:
version (DigitalMars_funky_extension)
{
...
}
올바른 목적에 올바른 버전 식별자를 사용하는 것이 중요해요. 예를 들어 벤더 고유 기능을 사용할 때는 벤더 식별자를, 운영 체제 고유 기능을 사용할 때는 운영 체제 식별자를 사용하세요.
Debug 조건 (Debug Condition)
DebugCondition:
debug
debug ( Identifier )
일반적으로 릴리스 빌드(release build)와 디버그 빌드(debug build) 두 종류의 프로그램 버전이 만들어져요. 디버그 빌드는 추가 오류 검사 코드, 테스트 하네스, pretty-printing 코드 등을 포함해요. debug 문은 자신의 문 본문을 조건부로 컴파일해요. 이는 C에서 #ifdef DEBUG / #endif 쌍으로 하는 것을 D가 하는 방식이에요.
-debug 스위치가 컴파일러에 전달되면 debug 조건이 충족돼요. debug ( Identifier ) 조건은 debug 식별자가 Identifier와 일치할 때 충족돼요.
class Foo
{
int a, b;
debug:
int flag;
}
Debug 문 (Debug Statement)
DebugCondition을 가진 ConditionalStatement를 DebugStatement라 불러요. DebugStatement는 pure, @nogc, nothrow, @safe 검사가 수행되지 않는다는 점에서 완화된 의미적 검사(semantic checks)를 가져요. DebugStatement는 pure, @nogc, nothrow, @safe 속성의 추론(inference)에도 영향을 주지 않아요.
Undefined Behavior: (미정의 동작) 이 검사들이 우회되므로, 코드가 올바른지 보장하는 것은 프로그래머의 몫이에요. 예를 들어
nothrow함수에서 예외를 던지는 것은 미정의 동작이에요.Best Practices: (모범 사례) 이는 엄격한 속성 검사를 우회하여 디버깅 도움을 제공하는 코드를 쉽게 삽입할 수 있게 해줘요. DebugStatement가 활성화된 릴리스 코드를 절대 배포하지 마세요.
Debug 명세 (Debug Specification)
DebugSpecification:
debug = Identifier ;
Debug 식별자는 명령줄 스위치 -debug=identifier나 DebugSpecification으로 설정돼요. Debug 명세는 자신이 나타난 모듈에만 영향을 주고 임포트된 모듈에는 영향을 주지 않아요. Debug 식별자는 version 식별자나 다른 심볼들과 독립적인 자신만의 이름 공간에 있어요.
debug 명세를 전방 참조하는 것은 불법이에요:
debug (foo) writeln("Foo");
debug = foo; // error, foo used before set
DebugSpecification은 모듈 스코프에만 나타날 수 있고, MixinExpression으로 생성될 수 없어요.
debug에 매개변수를 주어 다양한 디버그 빌드를 만들 수 있어요:
debug (identifier) { } // add in debug code if debug keyword is identifier
이들은 명령줄에서 설정된다고 여겨져요. 예: -debug=identifier.
Static If 조건 (Static If Condition)
StaticIfCondition:
static if ( AssignExpression )
AssignExpression은 boolean 타입으로 암시적으로 변환되고 컴파일 타임에 평가돼요. true로 평가되면 조건이 충족되고, false로 평가되면 충족되지 않아요. AssignExpression이 boolean 타입으로 암시적으로 변환될 수 없거나 컴파일 타임에 평가될 수 없으면 오류예요.
StaticIfCondition은 모듈, 클래스, 템플릿, struct, union, 또는 함수 스코프에 나타날 수 있어요. 함수 스코프에서 AssignExpression이 참조하는 심볼은 그 지점에서 표현식이 일반적으로 참조할 수 있는 것일 수 있어요.
const int i = 3;
int j = 4;
static if (i == 3) // ok, at module scope
int x;
class C
{
const int k = 5;
static if (i == 3) // ok
int x;
else
long x;
static if (j == 3) // error, j is not a constant
int y;
static if (k == 5) // ok, k is in current scope
int z;
}
template Int(int i)
{
static if (i == 32)
alias Int = int;
else static if (i == 16)
alias Int = short;
else
static assert(0); // not supported
}
Int!(32) a; // a is an int
Int!(16) b; // b is a short
Int!(17) c; // error, static assert trips
StaticIfCondition은 다음과 같은 점에서 IfStatement와 달라요:
- 문뿐 아니라 선언도 조건부로 컴파일하는 데 사용할 수 있어요.
- 조건부로 컴파일된 문에
{ }를 사용해도 새로운 스코프를 도입하지 않아요. - 충족되지 않은 조건의 경우, 조건부로 컴파일된 코드는 문법적으로만 올바르면 돼요. 의미적으로 올바를 필요는 없어요.
- 컴파일 타임에 평가 가능해야 해요.
Static Foreach
StaticForeachDeclaration:
StaticForeach DeclarationBlock
StaticForeach : DeclDefsopt
StaticForeachStatement:
StaticForeach NoScopeNonEmptyStatement
StaticForeach:
static AggregateForeach
static RangeForeach
static foreach는 자신의 DeclarationBlock이나 DeclDefs를 일련의 선언들로 확장해요. 각 선언은 선언된 ForeachType 심볼 중 어떤 것이든 참조할 수 있어요.
- 애그리게이트/범위 경계(bounds)는 컴파일 타임에 평가되고, ForeachStatement/ForeachRangeStatement로 해당 코드를 컴파일 타임에 평가하여 컴파일 타임 엔티티 시퀀스로 변환돼요.
- 그러면 static foreach의 본문은 시퀀스의 요소 수에 해당하는 횟수만큼 복사돼요.
- i번째 복사본 안에서 static foreach 요소 '변수'의 이름은 시퀀스의 i번째 항목에 바인딩돼요. 상수의 경우 enum 변수 선언으로, 심볼의 경우 alias 선언으로 바인딩돼요. (특히, static foreach 변수는 결코 런타임 변수가 아니에요.)
static foreach (i; [0, 1, 2, 3])
{
pragma(msg, i);
}
static foreach는 해당 foreach 문이 지원하는 경우 여러 개의 ForeachType 변수를 지원해요. (이 경우 static foreach는 컴파일 타임 튜플 시퀀스를 생성하고, 튜플은 순회 중에 풀려요(unpacked).)
static foreach (i, v; ['a', 'b', 'c', 'd'])
{
static assert(i + 'a' == v);
}
ConditionalDeclaration의 본문처럼, static foreach 본문은 새로운 스코프를 도입하지 않아요. 따라서 기존 스코프에 선언을 추가하는 데 사용할 수 있어요:
import std.range : iota;
static foreach (i; iota(0, 3))
{
mixin(`enum x`, i, ` = i;`);
}
pragma(msg, x0, " ", x1," ", x2); // 0 1 2
함수 안에서 각 확장에 새 스코프가 필요하면, 중괄호 세트를 하나 더 사용하세요:
void fun()
{
static foreach (s; ["hi", "hey", "hello"])
{{
enum len = s.length; // local to each iteration
static assert(len <= 5);
}}
static assert(!is(typeof(len)));
}
static AggregateForeach는 foreach처럼 시퀀스 확장뿐 아니라 ForeachTuplePatterns 언패킹도 지원해요.
break와 continue
static foreach는 코드 생성 구성(loop가 아님)이므로 break와 continue를 사용해 그 안에서 제어 흐름을 바꿀 수 없어요. 적절한 바깥 문을 break/continue하는 대신, 그런 사용은 오류를 산출해요 (오해를 방지하기 위함이에요).
int test(int x)
{
int r = -1;
switch(x)
{
static foreach (i; 0 .. 5)
{
case i:
r = i;
break; // error
}
default: break;
}
return r;
}
static foreach (i; 0 .. 10)
{
static assert(test(i) == (i < 5 ? i : -1));
}
명시적 break/continue 라벨을 사용하면 이 제한을 피할 수 있어요. (static foreach 자체는 명시적으로 라벨을 붙여도 break되거나 continue될 수 없다는 점에 주의하세요.)
int test(int x)
{
int r = -1;
Lswitch: switch(x)
{
static foreach (i; 0 .. 5)
{
case i:
r = i;
break Lswitch;
}
default: break;
}
return r;
}
static foreach (i; 0 .. 10)
{
static assert(test(i) == (i < 5 ? i : -1));
}
Static Assert
StaticAssert:
static assert ( ArgumentList ) ;
첫 번째 AssignExpression은 컴파일 타임에 평가되고 boolean 값으로 변환돼요. 값이 true면 static assert는 무시돼요. 값이 false면 오류 진단이 발행되고 컴파일이 실패해요.
실패 시, 이후의 AssignExpression들은 각각 문자열로 변환된 뒤 연결돼요. 결과 문자열은 오류 진단과 함께 출력돼요.
AssertExpression과 달리, StaticAssert는 충족되지 않은 조건부 안에 나타나지 않는 한 항상 컴파일러가 검사하고 평가해요.
void foo()
{
if (0)
{
assert(0); // never trips
static assert(0); // always trips
}
version (BAR)
{
}
else
{
static assert(0); // trips when version BAR is not defined
}
}
StaticAssert는 코드에서 지원되지 않는 조건부 구성에 주의를 끌기 위한 유용한 도구예요.