38

There was a discussion in the Linux kernel mailing list regarding a macro that tests whether its argument is an integer constant expression and is an integer constant expression itself.

One particularly clever approach that does not use builtins, proposed by Martin Uecker (taking inspiration from glibc's tgmath.h), is:

#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))

This macro expands into an integer constant expression of value 1 if the argument is an integer constant expression, 0 otherwise. However, it relies on sizeof(void) to be allowed (and different than sizeof(int)), which is a GNU C extension.

Is it possible to write such a macro without builtins and without relying on language extensions? If yes, does it evaluate its argument?


For an explanation of the macro shown above, see instead: Linux Kernel's __is_constexpr Macro

Acorn
  • 24,970
  • 5
  • 40
  • 69
  • You have not shown the macro's usage. Please post the [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) that shows the problem. – Weather Vane Mar 25 '18 at 20:37
  • 22
    @WeatherVane This isn't a debugging question ("why doesn't this code work?"). – melpomene Mar 25 '18 at 20:38
  • @melpomene why is that exempt? I would like to see how this is used. – Weather Vane Mar 25 '18 at 20:39
  • 4
    @WeatherVane See [your link](https://stackoverflow.com/help/mcve): "*When asking a question about a problem caused by your code ...*" It does not apply here. – melpomene Mar 25 '18 at 20:40
  • @melpomene I would avoid "clever" approaches and I would still like to see some context. – Weather Vane Mar 25 '18 at 20:45
  • 7
    @WeatherVane There's an example at https://lkml.org/lkml/2018/3/21/223; basically, the goal is to define e.g. a `MAX` macro that is both safe with effectful arguments and is a constant expression with constant arguments. Using an inline function or temporary variables fulfills #1 but not #2; using `?:` and repeating the argument expressions fulfills #2 but not #1. If you could detect constant expressions, you could have your cake and eat it too. – melpomene Mar 25 '18 at 20:45
  • There is something about `char *` and `void *` having same/compatible representations, so maybe use `(char*)(void*)((x)` to avoid `sizeof(void)`. – chux - Reinstate Monica Mar 26 '18 at 00:42
  • @chux that would fail to compile, as the conditional operator requires one operand be implicitly convertible to the type of the other – M.M Mar 29 '18 at 08:56
  • A bit off-topic: you may be interested: [check that variable has static storage-class specifier](https://stackoverflow.com/q/65520719/1778275). – pmor Sep 11 '22 at 20:49

2 Answers2

26

Use the same idea, where the type of a ?: expression depends on whether an argument is a null pointer constant or an ordinary void *, but detect the type with _Generic:

#define ICE_P(x) _Generic((1? (void *) ((x)*0) : (int *) 0), int*: 1, void*: 0)

Demo on Ideone. _Generic is a C11 addition, so if you're stuck on C99 or something earlier, you won't be able to use it.

Also, have standard links for the definition of a null pointer constant and the way null pointer constants interact with the type of a ?: expression:

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

and

If both the second and third operands are pointers or one is a null pointer constant and the other is a pointer, the result type is a pointer to a type qualified with all the type qualifiers of the types referenced by both operands. Furthermore, if both operands are pointers to compatible types or to differently qualified versions of compatible types, the result type is a pointer to an appropriately qualified version of the composite type; if one operand is a null pointer constant, the result has the type of the other operand; otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version of void.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • Indeed, `_Generic` solves that issue (similar to using a built-in like `__builtin_types_compatible_p`). The kernel does not use C11, but I didn't mark the question as C89 in order to get as many solutions as possible in different versions of the Standard. Let's see if someone comes up with a different approach! – Acorn Mar 25 '18 at 21:36
  • 1
    Probably also worthwhile to add links to explain the problem with `sizeof(void)`: §6.5.3.4¶1 and §6.2.5¶19. Although all this should probably go in the question not your answer… – nemequ Mar 25 '18 at 21:40
  • 2
    @Acorn: I haven't figured out anything that works in C89. Everything I can think of requires C++ or compiler extensions - stuff like decltype, typeof, SFINAE, function overloading, template specialization, etc. Also, it's pretty inconvenient that the `?:` trick only seems to let you choose between a `void *` or some other pointer type; if we could get it to choose between two function pointer types, we could test the `sizeof` the return value - or something simpler I overlooked, if we could choose between two object pointer types, we could just dereference and `sizeof`. – user2357112 Mar 25 '18 at 21:56
  • What suffix `_P` stands for? I see that GCC builtins may end with `_p`. Note: instead of suffix `_P` I expected prefix `_IS`. – pmor Jan 05 '23 at 16:54
  • 1
    @pmor: "Predicate", I think. – user2357112 Jan 05 '23 at 16:55
19

I don't have a fix for sizeof(void) not being standard, but you could work around the possibility that sizeof(void) == sizeof(int) by doing something like:

#define ICE_P(x) ( \
  sizeof(void) != \
  sizeof(*( \
    1 ? \
      ((void*) ((x) * 0L) ) : \
      ((struct { char v[sizeof(void) * 2]; } *) 1) \
    ) \
  ) \
)

I know it's not a full answer, but it's slightly closer…

Edit: I've done a bit of research about which solutions work on various compilers. I've encoded all of the following information in Hedley; see the HEDLEY_IS_CONSTANT, HEDLEY_REQUIRE_CONTEXPR, and HEDLEY__IS_CONSTEXPR macros. It's public domain, and a single header, so it's very easy to just drop into your project, or you can copy the bits you're interested in.

C11 Macro & Variants

user2357112's C11 macro should work on any C11 compiler, but SunCC and PGI are currently broken so you'll have to blacklist them. Also, IAR defines __STDC_VERSION__ in C++ mode, and this trick doesn't work in C++ (AFAIK nothing does), so you'll probably want to make sure __cplusplus isn't defined. I've verified that it really does work on GCC, clang (and clang-derived compilers like emscripten), ICC, IAR, and XL C/C++.

Other than that, some compilers support _Generic even in older modes as an extension:

  • GCC 4.9+
  • clang; check with __has_feature(c_generic_selections) (you may want to disable the -Wc11-extensions warning, though)
  • ICC 16.0+
  • XL C/C++ 12.1+

Also, note that sometimes compilers emit a warning when you cast an int to a void*; you can get around this by first casting to an intptr_t then to void*:

#define ICE_P(expr) _Generic((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)

Or, for compilers (such as GCC) which define __INTPTR_TYPE__, you can use that instead of intptr_t and you needn't include stdint.h.

Another possible implementation here is to use __builtin_types_compatible_p instead of _Generic. I'm not aware of any compilers where that would work but the original macro wouldn't, but it does get you out of a -Wpointer-arith warning:

#define IS_CONSTEXPR(expr) \
  __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)

This version should work with GCC back to 3.1, as well as compilers which define __GNUC__/__GNUC_MINOR__ to values which indicate ≥ 3.1 such as clang and ICC.

Macro from this answer

Any compiler which supports sizeof(void) should work, but there is a good chance you'll run into a warning (such as -Wpointer-arith). That said, AFAICT compilers which do support sizeof(void) seem to have always done so, so any version of these compilers should work:

  • GCC
  • Clang (and compilers built in in, which also define __clang__)
  • ICC (tested 18.0)
  • XL C/C++ (tested 13.1.6)
  • TI (tested 8.0)
  • TinyCC

__builtin_constant_p

Depending on your use case, it may be preferable to use __builtin_constant_p on compilers which support it. It's a bit more general (and more nebulous) than an integer constant expression; it just says that the compiler knows the value at compile-time. These compilers are known to support it:

  • GCC 3.1+
  • Clang
  • ICC (tested 18.0)
  • TinyCC 0.9.19+
  • armcc 5.04+
  • XL C/C++ (undocumented, but it definitely works in 13.1.6+)

If you're using the macro to choose between a code path which the compiler can constant fold if it knows the value at compile time but is slow at runtime and a code path which is a black box to the compiler but is fast at runtime, use __builtin_constant_p.

OTOH, if you want to check to make sure the value is really an ICE according to the standard, don't use __builtin_constant_p. As an example, here is a macro which will return expr if the expr is an ICE, but -1 if it isn't:

#if defined(ICE_P)
#  define REQUIRE_ICE(expr) (ICE_P(expr) ? (expr) : (-1))
#else
#  define REQUIRE_ICE(expr) (expr)
#endif

You can then use that when declaring an array in a macro if you the compiler to show an error if you use a VLA:

char foo[REQUIRE_ICE(bar)];

That said, GCC and clang both implement a -Wvla warning which you may want to use instead. The advantage of -Wvla is that it doesn't require source code modifications (i.e., you can just write char foo[bar];). The disadvantages are that it isn't as widely supported, and that using conformant array parameters will also trigger the diagnostic, so if you want to avoid lots of false positives this macro may be your best bet.

Compilers which don't support anything

  • MSVC
  • DMC

Ideas welcome :)

nemequ
  • 16,623
  • 1
  • 43
  • 62
  • I'd be interested to know why the down vote... AFAICT it's a reasonable improvement to the original, which is useful since it doesn't require C11 (MSVC anyone?), and I noted that it's only a partial answer. – nemequ Mar 26 '18 at 00:42
  • :) because it is partial :) –  Mar 26 '18 at 01:25
  • 3
    I guess SO isn't really designed for collaboration. Still, IMHO it's mildly helpful so I'd rather leave it up even if it costs some reputation. – nemequ Mar 26 '18 at 01:51
  • I upvoted you for adding a step toward the solution. – wallyk Mar 26 '18 at 03:44
  • 1
    There seems to be no overlap in your compiler compatibility list - no compilers are listed under multiple solutions. What's up with that? My answer definitely worked on GCC when I tried it (in C11 mode). (Also, I think you forgot it's 2018 now when you put that date in.) – user2357112 Apr 25 '18 at 16:31
  • When I created this list I was only interested in choosing between a code path which could be optimized away by the compiler (constant folding) and one which was fast a run-time. I've since split them up a bit, and have versions at https://github.com/nemequ/attic/blob/master/is_constant.h and in the dev branch of [Hedley](https://github.com/nemequ/hedley) (I'm planning on moving it to master soon). I'll update this answer when I have some free time (i.e., over the weekend). As for the date: ugh, fixed, thanks :/ – nemequ Apr 26 '18 at 01:45