Sane API design (AKA C's #ifdef hell)

H. S. Teoh hsteoh at qfbox.info
Thu Apr 16 23:59:53 UTC 2026


On Thu, Apr 16, 2026 at 09:13:30PM +0000, matheus via Digitalmars-d wrote:
> On Thursday, 16 April 2026 at 18:52:23 UTC, H. S. Teoh wrote:
> > ...
> 
> Interesting, another solution would be Separate Files + Build System,
> but in this case we are moving the problem from source to
> building/compiling time.

The nice thing about using separate files is that you avoid #ifdef /
version hell altogether.

The problem with this is that it makes the code hard to understand. When
your source tree has multiple versions of the same file, how do you know
which is the one that contains the bug you're trying to track down?
Which file is the actual target of a particular function call?  You have
to understand the build system, and we all know that build systems tend
to grow hairs over time, especially in large projects where the ability
to quickly locate the target of a function call is especially important.

If there's only 2-3 files, this isn't a problem.  When there are 15
different files, it's a BIG problem: every function call going through
any function in these files can end up in any one of the 15, and which
exactly depends on fiddly details in the build system that you may not
be privy to. (And lest you think I'm exaggerating: the project I work on
targets *72* different platforms... we have 72 different files that
specify how each platform is to be configured.  Thankfully, there isn't
any code in these files, and the #ifdef hell in files that *do* have
code is mostly confined to feature-based #ifdef identifiers rather than
individual platforms. But there are exceptions, and you do NOT want to
have to deal with those...)

Having code appear / disappear based on the build system is IMO a code
smell, even though there are cases where this is unavoidable.  Basically
you're introducing an alternation that's invisible in the code itself,
that can only be resolved by looking at build details.  And build
details can be arbitrarily complex.  Or worse, if you use build systems
with the modern philosophy that things are inferred for you
automatically, you'll have to dive into the rabbit hole of figuring out
what exactly was inferred, and why -- just to resolve the target of a
function call.  Not ideal, to put it mildly.

(The same argument can be made for code that overuse dynamic binding,
like heavily-OO code.  Or in C, code that overuse function pointers. The
former is a little more manageable because there are tools for working
with OO; the latter can be really evil because the func ptrs are ad hoc
rather than following, say, a class hierarchy, so they can literally
point to *anything*. Good like figuring out where your function calls
are going when there's a bug.)


> I recently asked a question about a similar problem to this one in
> Learn Forum, didn't get much traction.
> 
> I think it's hard to define elegant way to do this.
[...]

In general, targeting individual features in #ifdef / version makes more
sense than targeting more abstract entities like individual platforms
(which are essentially a collection of features).  E.g., like this:

```d
	version(linux) {
		version = featureA;
		version = featureB;
		version = featureC;
	}
	version(Windows) {
		version = featureA;
		version = featureD;
		version = featureF;
	}
	...

	version(featureA) {
		int myFunc() { ... }
	}
	version(featureB) {
		int myFunc2() { ... }
	}
	...
```

This is because sometimes platform definitions change (e.g., you decide
to support feature X on platform Y that you didn't support before, even
though it could be done on that platform -- maybe you didn't have the
resources to do it before.  Or you decide to remove a feature from
platform Y because it was causing too many problems).  If your #ifdefs /
version blocks target platforms, you'll have to go through every
occurrence of version(platformY) and review whether something needs to
be changed.  If you target individual features, all you have to do is to
add/remove that feature from platformY's version block, and no other
code will need to change.  Much less messy.


T

-- 
I got an email explaining how to read maps backwards.  It was spam.


More information about the Digitalmars-d mailing list