2

How to implement (compile-time) static assertion in function-like macros ?

There is good discussion, and lot of alternative for the case of injecting static assertion as "C" statement - expanding into variants of static_assert. The challenge in the case of function like object is that I cannot find a way (at least with GCC9) to include static_assert as part of expression.

Consider the following example, where I would like to check that the passed parameter is either green or blue, and would like to have compile time error, if it's neither. This is simplified example, to demonstrate the problem. Actual condition much more complex.

#include <stdbool.h>
#include <assert.h>
#include <stdio.h>

#define GREEN 1
#define YELLOW 2
#define RED 3

#define is_green(color) (color == GREEN ? true : color == YELLOW ? false : _Static_assert(!("Bad Color")))

int main(int argc, char **argv)
{
    bool g = is_green(GREEN) ;
    bool y = is_green(YELLOW) ;
    bool r = is_green(RED) ;                      // Should fail
}

I expect the above to produce compile time assertion error - on the line with 'Should fail' comment. Instead, I'm getting assertional 3 errors on every call to is_green: "expected expression before _Static_assert".

The core issue seems to be that "Static_assert" is not a function - it's more like a statement, therefore it can not be included in expressions, - directly, via a macro or inline function.

Already tried the following:

  • Wrap it into expression with (Static_assert(...), false)
  • Use GCC statement expressions ( { Static_assert(...), false } )
  • Use the GCC __builtin_choose_expr

In all cases, GCC does not allow Static_assert to be part of expression - be it assignment, or function call.

Mu current workaround, is to get compile time alert by putting 0/0 instead of the _Static_assert - GCC produce compile time error - division by zero. This is confusing to the development team.

#define is_green(color) (color == GREEN ? true : color == YELLOW ? false : 0/0 )

Any suggestion ?

To reiterate - goal is to be able to raise compile time assertion for function-like macros. At this time I

Yair

9
  • 2
    static_assert is neither a function nor a statement. It's a declaration. It's not executed. It's either there or not there. If it's in the not-taken branch of a conditional construct, be it an if, a conditional expression, or anything else, it's still there. Commented Jun 8, 2024 at 14:22
  • You should probably spell blue in the macro as BLUE rather than YELLOW if you are checking for green or blue. :) Commented Jun 8, 2024 at 14:52
  • 1
    This is not a good example of how to (try to) use a static assertion. Static assertions are evaluated by the compiler during compilation; they are not present in the executable. Syntactically, it is a declaration just like int x = 3; is a declaration. You won't find it easy to use in an expression, especially in a function-like macro that is supposed to produce a value. Commented Jun 8, 2024 at 15:03
  • This seems like an XY problem. What problem is using static asserts like this supposed to solve? Commented Jun 8, 2024 at 16:26
  • 1
    "add static assertion that will validate arguments at compile time" Pointer arguments? No chance. Pointers are not constant expressions. Commented Jun 8, 2024 at 17:43

2 Answers 2

7

There are two parts to this question.

  1. How to place a declaration like static_assert inside an expression (possibly expanded from a function-like macro)?
  2. How to make static_assert conditionally fired, where the condition is not a preprocessing one (that is, not im an #if or friends)?

Nornally the answer to both of these question is "you can't".

However with a bit of chutzpah, you can.

Remember that C has compound literals. Compound literals look somewhat like cast expressions. They have a type inside. A type can be a struct. Structs have member declarations and... drumroll... static_assert declarations inside. Let's test it!

#define SUCC ((struct {int z; static_assert(1==1);}){1}, 0)
#define FAIL ((struct {int z; static_assert(1==2);}){1}, 0)

int main()
{
  int x = SUCC;
  int y = FAIL; // fails
}

Demo Hurray!

Now what about the conditional part? You don't need it. If you want to assert that the colour is either green or yellow, just assert it.

#define is_green(color) ((struct {static_assert(color==GREEN||color==YELLOW, "Bad color!"); int z;}){1}, color == GREEN)

Demo

Of course it will work only if the argument to the macro is a constant expression.

Sign up to request clarification or add additional context in comments.

1 Comment

You did not talk about the comma operator in your solution, which is required to make the return type of the clause an int instead of the struct. In my environment GCC warns about the unused value in the first part of the comma ("value computed is not used"). I suggest a (void) before the compound literal to get rid of that.
1

You need not resort to non-standard GNU extensions. Assuming I have correctly understood your requirements, the following is what you might be looking for:

/**
 * Like C11's _Static_assert() except that it can be used in an expression.
 *
 * EXPR - The expression to check.
 * MSG  - The string literal of the error message to print only if EXPR evalutes
 *        to false.
 *
 * Always return true. */
#define STATIC_ASSERT_EXPR(EXPR, MSG)   \
    (!!sizeof( struct { static_assert ( (EXPR), MSG ); char c; } ))

If EXPR evaluated to nonzero, sizeof will return nonzero for the anonymous struct. !! would convert it to 1, so the macro would expand to 1, or true. The char c is present just so the struct is not empty.

Else, the program would fail to compile because of the failed assertion.

Note that EXPR is required to be a constant expression.

Reference: Handy C/C++ Preprocessor Macros.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.