www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Sane API design (AKA C's #ifdef hell)

reply "H. S. Teoh" <hsteoh qfbox.info> writes:
In D circles we often talk about API design, and D lends itself quite
well to proper API design.  But you still have to apply it correctly.

Just today, while investigating a bug at a work project, I discovered
this nasty piece of work:

```c
	// file1.c:
	int getMaxMacGuffin() { ... }

	int getMaxMacGuffinNewPlatform() {
		...
		int n = getMaxMacGuffin();
		... // do something else with n
		return n;
	}

	// file2.c:
	int myFunc1() {
		...
		#ifdef BUILDING_FOR_NEW_PLATFORM
		int max = getMaxMacGuffinNewPlatform();
		#else
		int max = getMaxMacGuffin();
		#endif
		...
	}

	// file3.c:
	int myFunc2() {
		...
		#ifdef BUILDING_FOR_NEW_PLATFORM
		int max = getMaxMacGuffinNewPlatform();
		#else
		int max = getMaxMacGuffin();
		#endif
		...
	}

	// ... and so on, for like 4-5 different files in different
	// unrelated functions.
```

The bug was that under some circumstances the max number of MacGuffins
displayed doesn't match the actual max number.  Raise your hands, anyone
who guessed what the problem is.

...

Yeah you guessed it, in some obscure corner somewhere, somebody called
getMaxMacGuffin() without the accompanying #ifdef hell, thus obtaining
the wrong value.

The elephant in the room is, WHY ARE THERE TWO DIFFERENT FUNCTIONS FOR
COMPUTING THE MAX NUMBER OF MACGUFFINS?!?!?!

A bit of digging reveals that getMaxMacGuffin was the original function,
and getMaxMacGuffinNewPlatform was added later.  Apparently, whoever
implemented the latter went a little overboard with the philosophy of
"don't touch the original code, just code around it".  The chosen
solution, however, is totally insane.  Why would you want to sprinkle your
#ifdef hell all over the project everywhere getMaxMacGuffin is called?!

The much saner solution is:

```c
// renamed from the original getMaxMacGuffin()
static int _getMaxMacGuffin() { ... }

static int getMaxMacGuffinNewPlatform() {
		...
		int n = _getMaxMacGuffin();
		... // do something else with n
		return n;
}

// New version of the function, containing the ONLY actually necessary
// #ifdef:
int getMaxMacGuffin() {
	#ifdef BUILDING_FOR_NEW_PLATFORM
		return getMaxMacGuffinNewPlatform();
	#else
		return _getMaxMacGuffin();
	#endif
}

// Then everywhere else, just always call getMaxMacGuffin and delete all
// the rest of the #ifdef hell
```

After this refactoring, I didn't even need to know where the bug was; it
automatically fixed itself because now getMaxMacGuffin will always do
the right thing.

//

Thankfully, in D we don't have #ifdef hell...

... or do we?  Although the example I encountered today was in C, one
can easily conceive of the D equivalent:

```d
	// file1.c:
	int getMaxMacGuffin() { ... }

	int getMaxMacGuffinNewPlatform() {
		...
		auto n = getMaxMacGuffin();
		... // do something else with n
		return n;
	}

	// file1.c:
	int myFunc1() {
		...
		version(NewPlatform)
			auto max = getMaxMacGuffinNewPlatform();
		else
			auto max = getMaxMacGuffin();
		...
	}

	... // and so on
```

So we don't have #ifdef hell in D, but we *do* have version hell here,
and we'd end up in exactly the same situation as in the C version.  The
solution, of course, is also the same: move the version block inside the
function, and if needed move the original function body into a helper
function.

Thankfully, among D circles we generally hate boilerplate and like to
use metaprogramming to reduce or eliminate it. Still, used wrongly, even
D's better constructs can still lead to versioning hell, like above.


T

-- 
Frank disagreement binds closer than feigned agreement.
Apr 16
next sibling parent reply matheus <matheus gmail.com> writes:
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. 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. Matheus.
Apr 16
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
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.
Apr 16
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 4/16/2026 4:59 PM, H. S. Teoh wrote:
 	version(linux) {
 		version = featureA;
 		version = featureB;
 		version = featureC;
 	}
 	version(Windows) {
 		version = featureA;
 		version = featureD;
 		version = featureF;
 	}
It's best to write this as: ```d version(linux) { version = featureA; version = featureB; version = featureC; } else version(Windows) { version = featureA; version = featureD; version = featureF; } else static assert(0, "unsupported operating system"); ``` This eliminates the future problem of "what is the default operating system?"
Apr 16
prev sibling next sibling parent reply Dejan Lekic <dejan.lekic gmail.com> writes:
On Thursday, 16 April 2026 at 23:59:53 UTC, H. S. Teoh wrote:

It becomes _much more_ complicated when you need to check for 
version, and depending on version declare functions. D does not 
offer good solution for this and that is where C preprocessor 
wins as we have to combine `version`s, enums, static ifs in order 
to achieve things that are quite simple with C preprocessor.

Those who believe D is good for this should write a binding for a 
complex library that supports all versions.
Apr 17
next sibling parent Serg Gini <kornburn yandex.ru> writes:
On Friday, 17 April 2026 at 08:08:57 UTC, Dejan Lekic wrote:.
 Those who believe D is good for this should write a binding for 
 a complex library that supports all versions.
Heeey! D is elegant!
Apr 17
prev sibling next sibling parent reply Dennis <dkorpel gmail.com> writes:
On Friday, 17 April 2026 at 08:08:57 UTC, Dejan Lekic wrote:
 It becomes _much more_ complicated when you need to check for 
 version, and depending on version declare functions. D does not 
 offer good solution for this and that is where C preprocessor 
 wins as we have to combine `version`s, enums, static ifs in 
 order to achieve things that are quite simple with C 
 preprocessor.

 Those who believe D is good for this should write a binding for 
 a complex library that supports all versions.
I'm looking at https://github.com/nigels-com/glew/tree/master and https://github.com/BindBC/bindbc-opengl, and I wouldn't say either is a winner. The effort needed to accommodate "complex library" and "all versions" by far eclipses the effort to write a little boilerplate like: ```D enum glSupport = (){ version(GL_46) return GLSupport.gl46; else version(GL_45) return GLSupport.gl45; else version(GL_44) return GLSupport.gl44; else version(GL_43) return GLSupport.gl43; else version(GL_42) return GLSupport.gl42; else version(GL_41) return GLSupport.gl41; else version(GL_40) return GLSupport.gl40; else version(GL_33) return GLSupport.gl33; else version(GL_32) return GLSupport.gl32; else version(GL_31) return GLSupport.gl31; else version(GL_30) return GLSupport.gl30; else return GLSupport.gl21; }(); ``` https://github.com/BindBC/bindbc-opengl/blob/d3c00b1cb6b11494ed84bb8d9879eb18baa16d6c/source/bindbc/opengl/config.d#L46-L59 I'm not saying D's version system is all flowers and sunshine, but personally I wish people were more keen on solving the "10000 lines of complex bindings" problem rather than the "100 lines of boilerplate" problem. ImportC has lots of potential here, although in practice the abundant use of non-standard C extensions and macro APIs still necessitates lots of manual work unfortunately.
Apr 17
next sibling parent reply Dejan Lekic <dejan.lekic gmail.com> writes:
On Friday, 17 April 2026 at 10:32:37 UTC, Dennis wrote:
 I'm looking at https://github.com/nigels-com/glew/tree/master 
 and https://github.com/BindBC/bindbc-opengl, and I wouldn't say 
 either is a winner.
Your example is a tip of the iceberg. People who read this thread would see your reply and think "ah, it is not so bad". I encourage them to look at the bindbc-opengl to understand how difficult it is to do good bindings for D. I've learned this the hard way over the years, and my latest bindings (HDF5 library) have shown me how difficult it really can be, and irony is - libhdf5 is not _that_ complicated compared to some other libraries that heavily rely on C preprocessor and have insane macros that are actually critical part of the "library".
Apr 17
parent reply Dennis <dkorpel gmail.com> writes:
On Friday, 17 April 2026 at 11:54:32 UTC, Dejan Lekic wrote:
 Your example is a tip of the iceberg. People who read this 
 thread would see your reply and think "ah, it is not so bad". I 
 encourage them to look at the bindbc-opengl to understand how 
 difficult it is to do good bindings for D.
Can you demonstrate how the C pre-processor would make bindbc-opengl easier?
Apr 17
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 18/04/2026 12:57 AM, Dennis wrote:
 On Friday, 17 April 2026 at 11:54:32 UTC, Dejan Lekic wrote:
 Your example is a tip of the iceberg. People who read this thread 
 would see your reply and think "ah, it is not so bad". I encourage 
 them to look at the bindbc-opengl to understand how difficult it is to 
 do good bindings for D.
Can you demonstrate how the C pre-processor would make bindbc-opengl easier?
There is no need for ImportC for this. There is a perfectly good description of OpenGL in XML format: https://github.com/KhronosGroup/OpenGL-Registry/blob/main/xml/gl.xml
Apr 17
prev sibling parent reply Dejan Lekic <dejan.lekic gmail.com> writes:
On Friday, 17 April 2026 at 12:57:23 UTC, Dennis wrote:
 Can you demonstrate how the C pre-processor would make 
 bindbc-opengl easier?
My replies were not advocating for the C pre-processor. They were just to make sure people are aware of the pain we who write bindings to C libraries need to suffer in order to do it right... In general, configuration management with D is terrible because DOD (D gOD) thought `version` is enough, and then you see code which first turns version(s) into enum(s), and then you see tons of static ifs in the code. This is probably one of the reasons why Rust people are rewriting everything in Rust. Dealing with C libraries that have part of the "API" in header files as insane macros, and part in the actual library (static or dynamic) is no fun.
Apr 17
next sibling parent reply Dennis <dkorpel gmail.com> writes:
On Friday, 17 April 2026 at 13:33:49 UTC, Dejan Lekic wrote:
 My replies were not advocating for the C pre-processor.
I interpreted "D does not offer good solution for this and that is where C preprocessor wins" as a call to adopt at least some of C's pre-processor features into D.
 They were just to make sure people are aware of the pain we who 
 write bindings to C libraries need to suffer in order to do it 
 right...
I agree, that's why I'm a fan of exploring solutions to that. I already mentioned ImportC, Rikki mentioned meta-data based generation (https://github.com/rikkimax/ogl_gen) which I have used as well. I've also been using my own tool (https://dkorpel.github.io/ctod/) for binding generation, and recently I've been trying out LLMs. They all have their strengths and weaknesses.
 In general, configuration management with D is terrible because 
 DOD (D gOD) thought `version` is enough, and then you see code 
 which first turns version(s) into enum(s), and then you see 
 tons of static ifs in the code.
And this is where my experience differs: the hassle always comes from the complexity of the library being constantly changing and the upstream headers / meta data being janky. Whenever people suggest "if we added version algebra to D we could remove these 50 lines from druntime" I think "I don't want to remove 50 lines, I want to remove the whole 150 000!". I prefer more automation and less configurations, not more advanced configuration management. But that's just how I see it, hence my question if you could provide examples of how better configuration management would benefit D code bases. I'm interested in different perspectives.
Apr 17
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 18/04/2026 2:31 AM, Dennis wrote:
     They were just to make sure people are aware of the pain we who
     write bindings to C libraries need to suffer in order to do it right...
 
 I agree, that's why I'm a fan of exploring solutions to that. I already 
 mentioned ImportC, Rikki mentioned meta-data based generation (https:// 
 github.com/rikkimax/ogl_gen <https://github.com/rikkimax/ogl_gen>) which 
 I have used as well. I've also been using my own tool (https:// 
 dkorpel.github.io/ctod/ <https://dkorpel.github.io/ctod/>) for binding 
 generation, and recently I've been trying out LLMs. They all have their 
 strengths and weaknesses.
Metadata generators can be quite good like with OpenGL, but there are others... such as COM which are absolutely horrid c++ mixed monstrosities. I'm still unsure if it was a good thing or not for Walter to never implement it. It was part of D's original design document to include support.
Apr 17
parent reply Kagamin <spam here.lot> writes:
On Friday, 17 April 2026 at 14:37:28 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 Metadata generators can be quite good like with OpenGL, but 
 there are others... such as COM which are absolutely horrid c++ 
 mixed monstrosities. I'm still unsure if it was a good thing or 
 not for Walter to never implement it. It was part of D's 
 original design document to include support.
Did I miss something? D supports COM, I wrote bindings for 7zip COM interfaces and it's as clean as you can get ```d interface IArchiveOpenCallback : IUnknown { enum ID=ArchiveIID(0x10); HRESULT SetTotal(in ulong* files, in ulong* bytes); HRESULT SetCompleted(in ulong* files, in ulong* bytes); } interface IProgress : IUnknown { enum ID=CommonIID(0, 5); HRESULT SetTotal(in ulong total); HRESULT SetCompleted(in ulong* completeValue); } interface IArchiveExtractCallback : IProgress { enum ID=ArchiveIID(0x20); HRESULT GetStream(uint index, ISequentialOutStream* outStream, int askExtractMode); HRESULT PrepareOperation(int askExtractMode); HRESULT SetOperationResult(int opRes); } ``` granted, 7zip doesn't use ifdef hell.
Apr 20
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 21/04/2026 5:57 AM, Kagamin wrote:
 On Friday, 17 April 2026 at 14:37:28 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 Metadata generators can be quite good like with OpenGL, but there are 
 others... such as COM which are absolutely horrid c++ mixed 
 monstrosities. I'm still unsure if it was a good thing or not for 
 Walter to never implement it. It was part of D's original design 
 document to include support.
Did I miss something? D supports COM, I wrote bindings for 7zip COM interfaces and it's as clean as you can get ```d interface IArchiveOpenCallback : IUnknown {     enum ID=ArchiveIID(0x10);     HRESULT SetTotal(in ulong* files, in ulong* bytes);     HRESULT SetCompleted(in ulong* files, in ulong* bytes); } interface IProgress : IUnknown {     enum ID=CommonIID(0, 5);     HRESULT SetTotal(in ulong total);     HRESULT SetCompleted(in ulong* completeValue); } interface IArchiveExtractCallback : IProgress {     enum ID=ArchiveIID(0x20);     HRESULT GetStream(uint index, ISequentialOutStream* outStream, int askExtractMode);     HRESULT PrepareOperation(int askExtractMode);     HRESULT SetOperationResult(int opRes); } ``` granted, 7zip doesn't use ifdef hell.
There is more to COM than this. https://learn.microsoft.com/en-us/windows/win32/com/idl-files
Apr 20
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 4/17/2026 6:33 AM, Dejan Lekic wrote:
 In general, configuration management with D is terrible because DOD (D gOD) 
 thought `version` is enough, and then you see code which first turns
version(s) 
 into enum(s), and then you see tons of static ifs in the code.
People have done the enum thing in D, and it broke everything and I had to fix it. A link to and example of what you're talking about? Perhaps I can help.
Apr 18
prev sibling next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 17 April 2026 at 10:32:37 UTC, Dennis wrote:
 The effort needed to accommodate "complex library" and "all 
 versions" by far eclipses the effort to write a little 
 boilerplate like:
Substitution static if for version opens the door for more order of eval bugs in the compiler. I've never been able to actually do it in a real world project. tbh though instead of versioning symbols i'd rather they be like marked... better to say "function foo was introduced in version 4.4 but you are using 4.3 compatibility flags" rather than "undefined symbol: foo"
Apr 17
next sibling parent reply Dennis <dkorpel gmail.com> writes:
On Friday, 17 April 2026 at 13:51:58 UTC, Adam D. Ruppe wrote:
 tbh though instead of versioning symbols i'd rather they be 
 like marked... better to say "function foo was introduced in 
 version 4.4 but you are using 4.3 compatibility flags" rather 
 than "undefined symbol: foo"
Perhaps adding a bool and string argument to the ` disabled(b, "needs version 4.3")` attribute?
Apr 17
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 18/04/2026 2:33 AM, Dennis wrote:
 On Friday, 17 April 2026 at 13:51:58 UTC, Adam D. Ruppe wrote:
 tbh though instead of versioning symbols i'd rather they be like 
 marked... better to say "function foo was introduced in version 4.4 
 but you are using 4.3 compatibility flags" rather than "undefined 
 symbol: foo"
Perhaps adding a bool and string argument to the ` disabled(b, "needs version 4.3")` attribute?
Given that it doesn't support string either, I'd suggest we bring it to the meeting for approval. Looks like an easy win.
Apr 17
prev sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 17 April 2026 at 14:33:42 UTC, Dennis wrote:
 Perhaps adding a bool and string argument to the ` disabled(b, 
 "needs version 4.3")` attribute?
Yeah, that'd probably help. I want a string on disabled in general and adding a bool is a good idea... i like it.
Apr 17
prev sibling parent reply libxmoc <libxmoc gmail.com> writes:
On Friday, 17 April 2026 at 13:51:58 UTC, Adam D. Ruppe wrote:
 On Friday, 17 April 2026 at 10:32:37 UTC, Dennis wrote:
 The effort needed to accommodate "complex library" and "all 
 versions" by far eclipses the effort to write a little 
 boilerplate like:
Substitution static if for version opens the door for more order of eval bugs in the compiler. I've never been able to actually do it in a real world project. tbh though instead of versioning symbols i'd rather they be like marked... better to say "function foo was introduced in version 4.4 but you are using 4.3 compatibility flags" rather than "undefined symbol: foo"
+1 On Friday, 17 April 2026 at 10:32:37 UTC, Dennis wrote:
 On Friday, 17 April 2026 at 08:08:57 UTC, Dejan Lekic wrote:
 It becomes _much more_ complicated when you need to check for 
 version, and depending on version declare functions. D does 
 not offer good solution for this and that is where C 
 preprocessor wins as we have to combine `version`s, enums, 
 static ifs in order to achieve things that are quite simple 
 with C preprocessor.

 Those who believe D is good for this should write a binding 
 for a complex library that supports all versions.
I'm looking at https://github.com/nigels-com/glew/tree/master and https://github.com/BindBC/bindbc-opengl, and I wouldn't say either is a winner. The effort needed to accommodate "complex library" and "all versions" by far eclipses the effort to write a little boilerplate like: ```D enum glSupport = (){ version(GL_46) return GLSupport.gl46; else version(GL_45) return GLSupport.gl45; else version(GL_44) return GLSupport.gl44; else version(GL_43) return GLSupport.gl43; else version(GL_42) return GLSupport.gl42; else version(GL_41) return GLSupport.gl41; else version(GL_40) return GLSupport.gl40; else version(GL_33) return GLSupport.gl33; else version(GL_32) return GLSupport.gl32; else version(GL_31) return GLSupport.gl31; else version(GL_30) return GLSupport.gl30; else return GLSupport.gl21; }(); ```
I'm not a fan of this mindset, both are valuable, besides, this is exactly the problem. When the idiomatic answer to a problem is hack.., we need to kill this workaround culture and give people something that actually works. dmd -define:GL=43 ``` module gl; import core.config; // dmd would build this on demand at import time void draw() { static if (define.GL >= 33) { // use vao } else { // fallback old opengl } } ``` This is just a random example, don't view this as a feature request.
Apr 18
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 18/04/2026 10:13 PM, libxmoc wrote:
 I'm not a fan of this mindset, both are valuable, besides, this is 
 exactly the problem. When the idiomatic answer to a problem is hack.., 
 we need to kill this workaround culture and give people something that 
 actually works.
 
 dmd -define:GL=43
 
 |module gl; import core.config; // dmd would build this on demand at 
 import time void draw() { static if (define.GL >= 33) { // use vao } 
 else { // fallback old opengl } } |
 
 This is just a random example, don't view this as a feature request.
OpenGL is a horrible example for versioning. A single executable could support 4.5, 3.3, 2 and pick at runtime. You load the OpenGL bindings twice, once for first context, then once you know what you need and created the second context you load again. I have wanted a config file available by dub automatically with such metadata. It would need to be per-binary. The main issue is you're going to run into is symbol conflict if you compile it in multiple times.
Apr 18
prev sibling parent Kagamin <spam here.lot> writes:
On Saturday, 18 April 2026 at 10:13:02 UTC, libxmoc wrote:
 ```D
 enum glSupport = (){
 	version(GL_46)       return GLSupport.gl46;
 	else version(GL_45)  return GLSupport.gl45;
 	else version(GL_44)  return GLSupport.gl44;
 	else version(GL_43)  return GLSupport.gl43;
 	else version(GL_42)  return GLSupport.gl42;
 	else version(GL_41)  return GLSupport.gl41;
 	else version(GL_40)  return GLSupport.gl40;
 	else version(GL_33)  return GLSupport.gl33;
 	else version(GL_32)  return GLSupport.gl32;
 	else version(GL_31)  return GLSupport.gl31;
 	else version(GL_30)  return GLSupport.gl30;
 	else                 return GLSupport.gl21;
 }();
 ```
I'm not a fan of this mindset, both are valuable, besides, this is exactly the problem. When the idiomatic answer to a problem is hack.., we need to kill this workaround culture and give people something that actually works.
AIU that version block is unnecessary, the author just wanted to do it, the docs say when you select opengl 4.6, it gives you not 4.6, but all versions from 1.0 to 4.6.
Apr 20
prev sibling next sibling parent Kapendev <alexandroskapretsos gmail.com> writes:
On Friday, 17 April 2026 at 10:32:37 UTC, Dennis wrote:
 I'm not saying D's version system is all flowers and sunshine, 
 but personally I wish people were more keen on solving the 
 "10000 lines of complex bindings" problem rather than the "100 
 lines of boilerplate" problem. ImportC has lots of potential 
 here,
A bit off topic, but generating bindings with ImportC is really nice. It has some weird parts of course, but nothing that can't be fixed with some extra steps.
Apr 17
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 4/17/2026 3:32 AM, Dennis wrote:
      else                 return GLSupport.gl21;
The `else` at the end should execute `static assert(0, "unsupported version");` or somebody is going to get an unpleasant surprise.
Apr 18
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 4/17/2026 1:08 AM, Dejan Lekic wrote:
 It becomes _much more_ complicated when you need to check for version, and 
 depending on version declare functions. D does not offer good solution for
this 
 and that is where C preprocessor wins as we have to combine `version`s, enums, 
 static ifs in order to achieve things that are quite simple with C
preprocessor.
 
 Those who believe D is good for this should write a binding for a complex 
 library that supports all versions.
This is going back to version algebra. I do understand how compelling having the algebra looks. But once you've been using it for a couple decades, across multiple diverse platforms, it starts to resemble a maze of twisty passages, all different. The problems that arise are: 1. Losing track of what the result of the algebra is. It gets hard to figure out if a branch is "live" or "dead". 2. Adding a new system breaks the algebra in ways that are not immediately obvious. 3. Nobody knows what system the 'else' clause is meant for. 4. It becomes too easy to break system A when one is fixing system B. 5. Lots of twisty algebra makes it really hard to get things exactly right for system A. When coding for system A, one has to pick apart the parts that don't apply. 6. It is really unhappy when a contributor folds in support for a new system, and the result breaks the other working systems. People don't believe me. Maybe 15 years ago, some people decided to use version algebra in druntime that relied on enums. Naturally, many bugs surfaced. This was dropped in my lap, and I removed all the algebra and replaced it with simple versions, and it has worked well since. I occasionally go through druntime and make all the else clauses say "static assert(0, "unsupported OS");" Homework assignment: pick a /usr/include .h file. Read the code and figure out which clauses are "live" without running cpp on it.
Apr 18
parent reply Dejan Lekic <dejan.lekic gmail.com> writes:
On Saturday, 18 April 2026 at 19:59:04 UTC, Walter Bright wrote:
 This is going back to version algebra.
I did not mention version algebra at all. I just said that with current state it is superhard to write good bindings for even slightly more complicated C library. Solution does not need to involve version algebra. The problem however needs brainstorming.
Apr 18
parent Walter Bright <newshound2 digitalmars.com> writes:
On 4/18/2026 1:05 PM, Dejan Lekic wrote:
 I did not mention version algebra at all. I just said that with current state
it 
 is superhard to write good bindings for even slightly more complicated C
library.
I might be able to help if you can post a specific example.
Apr 18
prev sibling parent reply Araq <rumpf_a web.de> writes:
On Thursday, 16 April 2026 at 23:59:53 UTC, H. S. Teoh wrote:
 (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.)
Actually they can only point to prototype compatible functions and you can use an IDE/langserver to find out where the function pointers are assigned to. Or you use a debugger and inspect the callstack. Welcome to the world of tooling from the 90ies.
Apr 17
parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Fri, Apr 17, 2026 at 03:16:48PM +0000, Araq via Digitalmars-d wrote:
 On Thursday, 16 April 2026 at 23:59:53 UTC, H. S. Teoh wrote:
 (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.)
Actually they can only point to prototype compatible functions and you can use an IDE/langserver to find out where the function pointers are assigned to. Or you use a debugger and inspect the callstack. Welcome to the world of tooling from the 90ies.
OO tooling is not perfect, but still better than home-grown ad hoc functions pointers thrown together in C, trying really hard to imitate OO but doing it really badly and on top of that having zero tooling support. T -- Why ask rhetorical questions? -- JC
Apr 17
prev sibling parent Kagamin <spam here.lot> writes:
On Thursday, 16 April 2026 at 18:52:23 UTC, H. S. Teoh wrote:
 ```c
 // New version of the function, containing the ONLY actually 
 necessary
 // #ifdef:
 int getMaxMacGuffin() {
 	#ifdef BUILDING_FOR_NEW_PLATFORM
 		return getMaxMacGuffinNewPlatform();
 	#else
 		return _getMaxMacGuffin();
 	#endif
 }
 ```
This design pattern is called "platform abstraction layer".
Apr 24