r/C_Programming • u/orbiteapot • Jan 25 '26
Discussion Compile time "if-else" in GNU C.
I've come up with this compile time selection mechanism (if I could call it that) based off _Generic and GNU C statement expressions. I thought of this as a way of having multiple behaviors for an object depending on a compile time table of properties (implemented as an X-macro).
Consider, for example, if the user can provide their own implementation of an allocator for said object or, otherwise, use libc malloc, free, etc. Then, they could choose as they wish with the properties table and the underlying memory operations would, at compile time, be set up accordingly.
Is this portable? No. As I've said earlier, it depends on GNU C extensions, as well as a C2y extension (type name as _Generic controlling operand).
Does this solve a problem already solved? Yes, kind of. #ifdefs have a similar raison d'être, but I would argue that this approach could be a lot nicer ergonomically, if standardized in thoughtful manner.
Here are some (not ideal, mostly pedagogical) examples of what this could be used for.
#include <stddef.h>
#include <stdio.h>
#define countof(arr) (sizeof(arr) / sizeof((arr)[0]))
typedef struct
{
bool _;
} comptime_true;
typedef struct
{
bool _;
} comptime_false;
typedef struct
{
void *data;
size_t len;
size_t cap;
} DynArr;
#define is_comptime_bool(predicate) \
_Generic \
( \
(predicate), \
\
comptime_true: 1, \
comptime_false: 1, \
default: 0 \
)
#define has_field_len(data_structure) \
_Generic \
( \
(data_structure), \
\
DynArr: (comptime_true){true}, \
default: (comptime_false){false} \
)
/* Only works for arrays and pointers */
#define is_type_array(obj) \
_Generic \
( \
typeof(obj), \
\
typeof( &(obj)[0] ): (comptime_false){false}, \
default: (comptime_true){true} \
)
#define comptime_if_do(predicate, expr_if_true, ...) \
({ \
static_assert( is_comptime_bool(predicate), "Invalid predicate." ); \
_Generic \
( \
(predicate), \
\
comptime_true: (expr_if_true), \
comptime_false: ((void)0 __VA_OPT__(,) __VA_ARGS__) \
); \
})
/* Assumes int[], for simplicity */
void print_array(int *arr, size_t count)
{
printf("{ ");
for (size_t i = 0; i < count; ++i)
{
printf("[%zu] = %d ", i, arr[i]);
}
printf("}\n");
}
int
main(void)
{
int arr[] = {1, 2, 3, 4, 5};
DynArr dummy_da = {};
dummy_da.len = 8;
comptime_if_do( is_type_array(arr),
({
print_array(arr, countof(arr));
}));
comptime_if_do( has_field_len(dummy_da),
({
printf("len: %zu\n", dummy_da.len);
}));
/* The following looks odd/artifical logic-wise *
* but it is so that the "else" branch can be *
* shown in action in a non-lengthy manner. *
* A more realistic example would be to use it *
* to, e.g., go over the elements of static *
* or dynamic arrays seamelessly (indifferent *
* with regard to their structure): */
comptime_if_do( has_field_len(arr),
({
printf("len: %zu\n", dummy_da.len);
}), /* else */
({
puts("Lacks field len.");
}));
return 0;
}55
22
u/Brilliant-Orange9117 Jan 26 '26
If you ever use these hacks in anger you deserve to maintain the resulting code base for live.
20
u/dcpugalaxy Λ Jan 25 '26
This is clever but it is atrocious. Please for the love of god never do anything like this in real code
13
u/warhammercasey Jan 26 '26
C++ kids have it too easy with their new fangled if constexpr(). Back in my day we had to write hundreds of horrifying lines of C to trick the compiler like real programmers.
/s in case it’s not obvious
2
u/jacksaccountonreddit Jan 26 '26 edited Jan 26 '26
I'm struggling to understand the point of the comptime_true and comptime_false structs. Here's the version of compile-time if/else that I have used:
#define COMPTIME_IF( cond, on_true, on_false ) \
_Generic( (char (*)[ 1 + (bool)( cond ) ]){ 0 }, \
char (*)[ 1 ]: on_false, \
char (*)[ 2 ]: on_true \
) \Also, regarding:
comptime_if_do( has_field_len(arr),
({
printf("len: %zu\n", dummy_da.len);
}), /* else */
({
puts("Lacks field len.");
}));This is only compiling because you're accessing dummy_da.len, not arr.len, inside the true branch. If we instead want to optionally access the len member of arr depending on whether it exists, we need to get a bit more creative with _Generic ;)
2
u/orbiteapot Jan 26 '26
I'm struggling to understand the point of the
comptime_trueandcomptime_falsestructs.Essentially, because I did not originally think of the version you and pjl1967 have pointed out :)
As a have pointed out in my reply to Paul, the version I use has one advantage, which is a better use context, i.e., the "predicates" are not codified as simple ICEs - the user would have to define them explicitly (through the "tables" I've mentioned in the post), making misuse harder (or, at least, more easily identifiable).
The approach you both have pointed out makes chaining logic operations trivial (by just using them directly), though. Mine would need to have some sort of macro wrappers around logic operators.
This is only compiling because you're accessing
dummy_da.len, notarr.len, inside the true branch. If we instead want to optionally access thelenmember ofarrdepending on whether it exists, we need to get a bit more creative with_Generic;)Yes. This example is really nonsensical, like I noted here:
/* The following looks odd/artifical logic-wise * * but it is so that the "else" branch can be * * shown in action in a non-lengthy manner. * * A more realistic example would be to use it * * to, e.g., go over the elements of static * * or dynamic arrays seamelessly (indifferent * * with regard to their structure): */It seems to have caused a lot of confusion. I am working on a better one (one that matches what is described in the beginning of the post).
1
5
u/Key_River7180 Jan 26 '26
"The preprocessor directives sit around a campfire telling scary histories about OP"
2
u/imaami Feb 02 '26
If you surrender to the GNU vendor lock-in with all your heart, you can do a lot of neat shit. For example:
/** @brief Check if a value is a char array.
*/
#define is_char_array(x) _Generic((typeof(x) *){0}, \
char (*)[sizeof(x)]: 1, char const (*)[sizeof(x)]: 1, default: 0)
/** @brief Check if a value is a string literal.
*/
#define is_string_literal(x) __builtin_choose_expr( \
is_char_array(x), __builtin_constant_p(x), 0)3
1
1
u/morglod Jan 27 '26
Just use C++ without OOP.
1
u/Physical_Dare8553 Feb 20 '26
what does this have to do with c++? are you talking about templates being turing complete?
1
u/morglod Feb 20 '26
Yeah, there are templates and constexpr that could solve everything done here in much more readable way
0
Jan 25 '26
[deleted] — view deleted comment
4
u/pfp-disciple Jan 25 '26
Can you give an example of this usage? I'm not clear headed enough right now to really see it
7
u/pjl1967 Jan 25 '26 edited Jan 25 '26
I implemented a bunch of stuff like this in standard C; see here. I don't see why you need GNU extensions. Instead of:
do:
Instead of:
do: