digitalmars.D - Sane API design (AKA C's #ifdef hell)
- H. S. Teoh (111/111) Apr 16 In D circles we often talk about API design, and D lends itself quite
- matheus (8/9) Apr 16 Interesting, another solution would be Separate Files + Build
- H. S. Teoh (74/84) Apr 16 The nice thing about using separate files is that you avoid #ifdef /
- Walter Bright (16/26) Apr 16 It's best to write this as:
- Dejan Lekic (8/8) Apr 17 On Thursday, 16 April 2026 at 23:59:53 UTC, H. S. Teoh wrote:
- Serg Gini (3/5) Apr 17 Heeey!
- Dennis (31/39) Apr 17 I'm looking at https://github.com/nigels-com/glew/tree/master and
- Dejan Lekic (10/13) Apr 17 Your example is a tip of the iceberg. People who read this thread
- Dennis (3/7) Apr 17 Can you demonstrate how the C pre-processor would make
- Richard (Rikki) Andrew Cattermole (4/12) Apr 17 There is no need for ImportC for this.
- Dejan Lekic (12/14) Apr 17 My replies were not advocating for the C pre-processor. They were
- Dennis (22/30) Apr 17 I interpreted "D does not offer good solution for this and that
- Richard (Rikki) Andrew Cattermole (6/16) Apr 17 Metadata generators can be quite good like with OpenGL, but there are
- Kagamin (27/32) Apr 20 Did I miss something? D supports COM, I wrote bindings for 7zip
- Richard (Rikki) Andrew Cattermole (3/38) Apr 20 There is more to COM than this.
- Walter Bright (3/6) Apr 18 People have done the enum thing in D, and it broke everything and I had ...
- Adam D. Ruppe (8/11) Apr 17 Substitution static if for version opens the door for more order
- Dennis (3/7) Apr 17 Perhaps adding a bool and string argument to the `@disabled(b,
- Richard (Rikki) Andrew Cattermole (4/12) Apr 17 Given that it doesn't support string either, I'd suggest we bring it to
- Adam D. Ruppe (3/5) Apr 17 Yeah, that'd probably help. I want a string on disabled in
- libxmoc (22/65) Apr 18 +1
- Richard (Rikki) Andrew Cattermole (9/21) Apr 18 OpenGL is a horrible example for versioning.
- Kagamin (4/24) Apr 20 AIU that version block is unnecessary, the author just wanted to
- Kapendev (5/10) Apr 17 A bit off topic, but generating bindings with ImportC is really
- Walter Bright (3/4) Apr 18 The `else` at the end should execute `static assert(0, "unsupported vers...
- Walter Bright (24/31) Apr 18 This is going back to version algebra.
- Dejan Lekic (6/7) Apr 18 I did not mention version algebra at all. I just said that with
- Walter Bright (2/4) Apr 18 I might be able to help if you can post a specific example.
- Araq (5/13) Apr 17 Actually they can only point to prototype compatible functions
- H. S. Teoh (8/21) Apr 17 OO tooling is not perfect, but still better than home-grown ad hoc
- Kagamin (2/14) Apr 24 This design pattern is called "platform abstraction layer".
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
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
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: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.)...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.[...] 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
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
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
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
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
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
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
On 18/04/2026 12:57 AM, Dennis wrote:On Friday, 17 April 2026 at 11:54:32 UTC, Dejan Lekic wrote: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.xmlYour 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
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
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
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
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
On 21/04/2026 5:57 AM, Kagamin wrote:On Friday, 17 April 2026 at 14:37:28 UTC, Richard (Rikki) Andrew Cattermole wrote:There is more to COM than this. https://learn.microsoft.com/en-us/windows/win32/com/idl-filesMetadata 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
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
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
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
On 18/04/2026 2:33 AM, Dennis wrote:On Friday, 17 April 2026 at 13:51:58 UTC, Adam D. Ruppe wrote:Given that it doesn't support string either, I'd suggest we bring it to the meeting for approval. Looks like an easy win.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
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
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:+1 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"On Friday, 17 April 2026 at 08:08:57 UTC, Dejan Lekic 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.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; }(); ```
Apr 18
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
On Saturday, 18 April 2026 at 10:13:02 UTC, libxmoc wrote: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.```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.
Apr 20
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
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
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
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
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
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
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: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(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
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









Walter Bright <newshound2 digitalmars.com> 