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