Introduction
Among other things, C11 added the _Generic keyword that enables compile-time selection of an expression based on the type o...
For further actions, you may consider blocking this person and/or reporting abuse
One problem I'm encountering when trying to build generic API is my inability to detect if a struct has a member—something like a
HAS_ATTRIBUTE
macro.For example, we could redefine
STRLEN
by leveraging yourSTATIC_IF
and this hypotheticalHAS_ATTRIBUTE
macro:This example is a bit naive, but this kind of pattern would be useful for building, let's say a linear algebra library.
Thanks for your writing Paul, I'm returning to this article because it's a fantastic read.
Being able to determine whether a
struct
has a specific member is beyond the capabilities of the preprocessor — it doesn't understand C. And you can't use_Generic
since you could never check for something (like astruct
member) not existing because the only way you could know that is by compiling a small piece of test code that uses said member: if it compiles, it exists — but if it does not exist, then the program will fail to compile.Typically, such things are done at "configure" time. For example, autoconf has
AC_CHECK_MEMBER
that then defines a macro (or not) based on the result.I use it in one of my projects to check whether
struct passwd
contains apw_dir
member: if so, autoconf definesHAVE_STRUCT_PASSWD_PW_DIR
for which you can then use#if
or#ifdef
(for example).BTW,
__has_attribute
exists, but tests whether the compiler supports a particular attribute, not whether astruct
has a specific member.BTW2, I occasionally retroactively add stuff to this article, e.g., I recently added
IS_SAME()
.Thanks that makes sense. If you can add a build step Autoconf seems like the perfect solution for this.
That said, I will continue my research, after all, the macro world is full of surprises
You're right. My mistake. I was thinking about zig's
@hasField
.That's great ! Maybe this topic (c macro, API design) is worth its own series. :)
As always, thanks for your answer!
That preprocessor iceberg is quite something! When I have more time, I'll have to go through it in detail.
I knew you'd like it :)
Some of them are fascinating. That said, they are a bit too magical for using in a codebase, IMO.
Here are some of my favorites:
I've been trying to put this article teaching in pratices by building an
IS_WITHIN
macro that would not generate a warning when used with a unsigned number.My implementation looks like this.
Saddly, this macro has the same problem as
STRLEN
: bothSTATIC_IF
branches is compiled and therefore the warning is still generated. UsingONLY_IF_T
doesn't seems to work because of the lack of functions.Does anybody has an idea on how to create a
STATIC_IF
that ignores the invalid condition?You don't need
STATIC_IF
:This compiles with no warnings for unsigned types, at least with my compiler. The trick is to split
>=
into>
||
==
. Neither subexpression is always true and the compiler doesn't realize that the combination is always true whenMIN
is0
.Don't fall into the trap of trying to use things like
STATIC_IF
where they're really not needed and overcomplicating the solution.Hi Paul,
Thanks for you reply.
I just tested your solution with
-Wall -Wextra -Wpedantic -Wconversion
-Wall -Wextra -Wpedantic -Wconversion
/W4
and everything looks great: no warning generated.You're absolutly rigth. I try to have as little complex macros as possible.
I didn't think about spliting the operator in two.
To be honest, I'm supprise this even works.
Best,
Gabriel
Lots of great stuff in this article, thanks! But perhaps a cleaner approach to the lack of SFINAE could be:
What makes that cleaner?
In my view it is cleaner because it treats each type identically, and doesn't require an extra dummy function.
It's unfortunate that either technique requires that each clause to repeat the type name but I don't see any way around that.
But does it have the same mistake-catching ability? My implementation will result in an undefined function at link-time. Your implementation won't.
In this particular case, if the
ONLY_IF_T(strbuf*,...)
case ends up being selected because the programmer made a mistake, then the result would be using0
(a null pointer) for->len
and the program crashing at run-time rather than link-time, no?I don't think so, for the same reason that yours can safely call strlen((const chr *)(S)) even when the type is strbuf *.
But perhaps you're imagining a different type of mistake? I would have no problem with either technique failing if the programmer is selecting for type T and then casts the value to some different type T1.
On another note, tcc seems to allow SFINAE (at least sometimes). It's so much more pleasant, for the life of me I don't understand why "they" wrote the standard as they did...
For the
strlen( (char const*)s )
, case, perhaps I was a bit lazy. You could useONLY_IF_T()
there as well so its check would be for both cases.I don't have the link handy, but I remember reading that the reason SFINAE isn't allowed in C is because it would have been adding a whole new concept that would be used only by
_Generic
and nowhere else — and the committee thought that was too much. In C++, SFINAE was added as part of templates — a large feature — since circa 1990.About
TO_VOID_PTR
, you might wish to see this StackOverflow question I asked related to it: error: pointer/integer type mismatch in conditional expression.For others reading, it’s not an error, but merely a warning. @notgiven chose to add
-Werror
.