Request assistance converting C's #ifndef to D

tsbockman via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Thu May 12 23:01:26 PDT 2016


On Friday, 13 May 2016 at 04:59:23 UTC, Andrew Edwards wrote:
> Is there a way to reproduce the same behavior? Are there 
> reason's for not allowing this functionality or am I just 
> misunderstanding and going about things the wrong way?
>
> [1] same result whether placed before or after the 
> #include/import statement.
>
>>> though I'm not sure if such a thing will actually work, since 
>>> order-dependent declarations in D are a kind of dangerous 
>>> territory to tread on.

C's #include directive and D's import statement have 
fundamentally different semantics:

C's is basically equivalent to copy-pasting the contents of the 
included file at the location of the #include directive. Combined 
with C's other pre-processor features, this means that a header 
file can (potentially) do something *completely* different in 
each file that includes it, because it is being recompiled from 
scratch, in a new context.

D's import statement, on the other hand, merely makes one 
module's symbols visible from another. Each module is only 
compiled once, regardless of how many times it is imported. As 
such, a module's semantics cannot be changed by each importer, 
unlike in C.

>> So what is the current best practice when encountering such 
>> statements during porting?

The affected code should be re-factored to use some other means 
of configuration, such as:

1) Adding configuration parameters individually to each public 
symbol as needed:

minA.d
=========================
     import minB;

     void main()
     {
         print!10();
     }

minB.d
=========================
     module minB;

     void print(long MIN)()
     {
         import std.stdio: writeln;
         writeln(MIN);
     }

2) Enclosing the module in a `struct` or `class` which can be 
instantiated and configured individually by each user, either at 
run time or compile time:

minA.d
=========================
     import minB;
     immutable minB = MinB(10);

     void main()
     {
         minB.print();
     }

minB.d
=========================
     module minB;

     struct MinB {
         long MIN;

         void print()
         {
             import std.stdio: writeln;
             writeln(MIN);
         }
     }

3) Enclosing the module in a `template` that can be instantiated 
and configured by each user at compile time:

minA.d
=========================
     import minB;
     alias minB = MinB!10;
     // Or: alias print = MinB!(10).print;

     void main()
     {
         minB.print();
         // Or: print();
     }

minB.d
=========================
     module minB;

     template MinB(long MIN) {
         void print()
         {
             import std.stdio: writeln;
             writeln(MIN);
         }
     }

4) Passing `version` identifiers at compile time (using the 
-version command line switch):

minA.d
=========================
     import minB;

     void main()
     {
         print!10();
     }

minB.d
=========================
     module minB;

     version(Min10) {
         enum MIN = 10;
     } else {
         enum MIN = 100;
     }

     void print()
     {
         import std.stdio: writeln;
         writeln(MIN);
     }

5) As a last resort, enclosing the module in a `mixin template` 
that can be mixed in to each importing module with the required 
configuration options at compile time:

minA.d
=========================
     import minB;
     mixin minB!10;

     void main()
     {
         print();
     }

minB.d
=========================
     module minB;

     mixin template minB(long MIN) {
         void print()
         {
             import std.stdio: writeln;
             writeln(MIN);
         }
     }

(5) is the most similar to how the C example works, but most of 
the time it is a very inefficient solution, causing tons of code 
bloat and redundant instantiations of the included functions and 
data structures. Don't do this unless you can't find any other 
reasonable way to do it.

Which of (1) to (4) is best just depends on exactly what the 
pre-processor logic was being used for.


More information about the Digitalmars-d-learn mailing list