www.digitalmars.com Home | Search | D | Comments
Last update Fri Aug 26 2005
D
Language
Phobos
Comparisons

· Lexical
· Modules
· Declarations
· Types
· Properties
· Attributes
· Pragmas
· Expressions
· Statements
· Arrays
· Structs & Unions
· Classes
· Interfaces
· Enums
· Functions
· Operator Overloading
· Templates
· Mixins
· Contracts
· Conditional Compilation
· Handling errors
· Garbage Collection
· Memory Management
· Floating Point
· Inline Assembler
· Interfacing To C
· Portability Guide
· Embedding D in HTML
· Named Character Entities
· Application Binary Interface

Conditional Compilation

Conditional compilation is the process of selecting which code to compile and which code to not compile. (In C and C++, conditional compilation is done with the preprocessor directives #if / #else / #endif.)
	ConditionalDeclaration:
	    Condition DeclarationBlock
	    Condition DeclarationBlock else DeclarationBlock

	DeclarationBlock:
	    Declaration
	    { Declarations }

	Declarations:
	    Declaration
	    Declaration Declarations

	ConditionalStatement:
	    Condition Statement
	    Condition Statement else Statement
	
If the Condition is satisfied, then the following DeclarationBlock or Statement is compiled in. If it is not satisfied, the DeclarationBlock or Statement after the optional else is compiled in.

Any DeclarationBlock or Statement that is not compiled in still must be syntactically correct.

No new scope is introduced, even if the DeclarationBlock or Statement is enclosed by { }.

ConditionalDeclarations and ConditionalStatements can be nested.

The StaticAssert can be used to issue errors at compilation time for branches of the conditional compilation that are errors. Condition comes in the following forms:

	Condition:
	    VersionCondition
	    DebugCondition
	    StaticIfCondition
	    IfTypeCondition
	

Version Condition

Versions enable multiple versions of a module to be implemented with a single source file.

	VersionCondition:
		version ( Integer )
		version ( Identifier )
	
The VersionCondition is satisfied if the Integer is greater than or equal to the current version level, or if Identifier matches a version identifier.

The version level and version identifier can be set on the command line by the -version switch or in the module itself with a VersionSpecification, or they can be predefined by the compiler.

Version identifiers are in their own unique name space, they do not conflict with debug identifiers or other symbols in the module. Version identifiers defined in one module have no influence over other imported modules.

	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 Specification

	VersionSpecification
	    version = Identifier ;
	    version = Integer ;
	
The version specification makes it straightforward to group a set of features under one major version, for example:
	version (ProfessionalEdition)
	{
	    version = FeatureA;
	    version = FeatureB;
	    version = FeatureC;
	}
	version (HomeEdition)
	{
	    version = FeatureA;
	}
	...
	version (FeatureB)
	{
	    ... implement Feature B ...
	}
	
Version identifiers or levels may not be forward referenced:
	version (Foo)
	{
	    int x;
	}
	version = Foo;	// error, Foo already used
	
While the debug and version conditions superficially behave the same, they are intended for very different purposes. Debug statements are for adding debug code that is removed for the release version. Version statements are to aid in portability and multiple release versions.

Here's an example of a full version as opposed to a demo version:

	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
		}
	    }
	}
	
Various different version builds can be built with a parameter to version:
	version(n) { }	// add in version code if version level is >= n
	version(identifier) { } // add in version code if version keyword is identifier
	
These are presumably set by the command line as -version=n and -version=identifier.

Predefined Versions

Several environmental version identifiers and identifier name spaces are predefined for consistent usage. Version identifiers do not conflict with other identifiers in the code, they are in a separate name space. Predefined version identifiers are global, i.e. they apply to all modules being compiled and imported.
DigitalMars
Digital Mars is the compiler vendor
X86
Intel and AMD 32 bit processors
X86_64
AMD and Intel 64 bit processors
Windows
Microsoft Windows systems
Win32
Microsoft 32 bit Windows systems
Win64
Microsoft 64 bit Windows systems
linux
All linux systems
LittleEndian
Byte order, least significant first
BigEndian
Byte order, most significant first
D_InlineAsm
Inline assembler is implemented
none
Never defined; used to just disable a section of code
all
Always defined; used as the opposite of none
Others will be added as they make sense and new implementations appear.

It is inevitable that the D language will evolve over time. Therefore, the version identifier namespace beginning with "D_" is reserved for identifiers indicating D language specification or new feature conformance.

Furthermore, predefined version identifiers from this list cannot be set from the command line or from version statements. (This prevents things like both Windows and linux being simultaneously set.)

Compiler vendor specific versions can be predefined if the trademarked vendor identifier prefixes it, as in:

	version(DigitalMars_funky_extension)
	{
	    ...
	}
	
It is important to use the right version identifier for the right purpose. For example, use the vendor identifier when using a vendor specific feature. Use the operating system identifier when using an operating system specific feature, etc.

Debug Condition

Two versions of programs are commonly built, a release build and a debug build. The debug build includes extra error checking code, test harnesses, pretty-printing code, etc. The debug statement conditionally compiles in its statement body. It is D's way of what in C is done with #ifdef DEBUG / #endif pairs.
	DebugCondition:
	    debug
	    debug ( Integer )
	    debug ( Identifier )
	
The debug condition is satisfied when the -debug switch is thrown on the compiler.

The debug ( Integer ) condition is satisfied when the debug level is >= Integer.

The debug ( Identifier ) condition is satisfied when the debug identifier matches Identifier.

	class Foo
	{
		int a, b;
	    debug:
		int flag;
	}
	

Debug Specification

	DebugSpecification
	    debug = Identifier ;
	    debug = Integer ;
	
Debug identifiers and levels are set either by the command line switch -debug or by a DebugSpecification.

Debug specifications only affect the module they appear in, they do not affect any imported modules. Debug identifiers are in their own namespace, independent from version identifiers and other symbols.

It is illegal to forward reference a debug specification:

	debug (foo) printf("Foo\n");
	debug = foo;	// error, foo used before set
	
Various different debug builds can be built with a parameter to debug:
	debug(Integer) { }	// add in debug code if debug level is >= Integer
	debug(identifier) { } // add in debug code if debug keyword is identifier
	
These are presumably set by the command line as -debug=n and -debug=identifier.

Static If Condition

	StaticIfCondition:
	    static if ( AssignExpression )
	
AssignExpression is implicitly converted to a boolean type, and is evaluated at compile time. The condition is satisfied if it evaluates to true. It is not satisfied if it evaluates to false.

It is an error if AssignExpression cannot be implicitly converted to a boolean type or if it cannot be evaluated at compile time.

StaticIfConditions cannot appear at module scope. They can appear in class, template, struct, union, or function scope. In function scope, the symbols referred to in the AssignExpression can be any that can normally be referenced by an expression at that point. Otherwise, the only symbols that can be referred to are up one scope level from the condition.

	const int i = 3;
	int j = 4;

	static if (i == 3)	// error, 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)	// error, k is in current scope
		int z;
	}

	template INT(int i)
	{
	    static if (i == 32)
		alias int INT;
	    else static if (i == 16)
		alias short INT;
	    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
	
A StaticIfConditional condition differs from an IfStatement in the following ways:
  1. It can be used to conditionally compile declarations, not just statements.
  2. It does not introduce a new scope even if { } are used for conditionally compiled statements.
  3. For unsatisfied conditions, the conditionally compiled code need only be syntactically correct. It does not have to be semantically correct.
  4. It must be evaluatable at compile time.

Iftype Condition

	IftypeCondition:
	    iftype ( Type )
	    iftype ( Type : TypeSpecialization )
	    iftype ( Type == TypeSpecialization )
	    iftype ( Type Identifier )
	    iftype ( Type Identifier : TypeSpecialization )
	    iftype ( Type Identifier == TypeSpecialization )

	TypeSpecialization:
	    Type
	
IftypeCondition is analogous to StaticIfCondition except that it is for types rather than expressions. There are several forms, corresponding to the three definitions above:
  1. The condition is satisfied if Type is semantically correct (it must be syntactically correct regardless).
    	alias int func(int);	// func is a alias to a function type
    	void foo()
    	{
    	    iftype (func[])	// not satisfied because arrays of
    				// functions are not allowed
    		printf("satisfied\n");
    	    else
    		printf("not satisfied\n");
    	}
    	
  2. The condition is satisfied if Type is semantically correct and it is the same as or can be implicitly converted to TypeSpecialization.
    	alias short bar;
    	void foo(bar x)
    	{
    	    iftype (bar : int)	// satisfied because short can be
    				// implicitly converted to int
    		printf("satisfied\n");
    	    else
    		printf("not satisfied\n");
    	}
    	
  3. The condition is satisfied if Type is semantically correct and is the same type as TypeSpecialization.
  4. The condition is satisfied if Type is semantically correct. If so, Identifier is declared to be an alias of Type.
    	alias short bar;
    	void foo(bar x)
    	{
    	    iftype (bar T)
    		alias T S;
    	    else
    		alias long S;
    
    	    writefln(typeid(S));	// prints "short"
    	}
    	
  5. The condition is satisfied if Type is the same as or can be implicitly converted to TypeSpecialization. The Identifier is declared to be either an alias of the TypeSpecialization or, if TypeSpecialization is dependent on Identifier, the deduced type.
    	alias short bar;
    	alias long* abc;
    	void foo(bar x, abc a)
    	{
    	    iftype (bar T : int)
    		alias T S;
    	    else
    		alias long S;
    
    	    writefln(typeid(S));	// prints "int"
    
    	    iftype (abc U : U*)
    		U u;
    
    	    writefln(typeid(typeof(u));	// prints "long"
    	}
    	
    The way the type of Identifier is determined is analogous to the way template parameter types are determined by TemplateTypeParameterSpecialization.
  6. The condition is satisfied if Type is semantically correct and is the same as TypeSpecialization. The Identifier is declared to be either an alias of the TypeSpecialization or, if TypeSpecialization is dependent on Identifier, the deduced type.

Static Assert

	StaticAssert:
	    static assert ( Expression );
	
Expression is evaluated at compile time, and converted to a boolean value. If the value is true, the static assert is ignored. If the value is false, an error diagnostic is issued and the compile fails.

Unlike AssertExpressions, StaticAsserts are always checked and evaluted by the compiler unless they appear in an unsatisfied conditional.

	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 is useful tool for drawing attention to conditional configurations not supported in the code.

Feedback and Comments

Add feedback and comments regarding this page.