Search code examples
cmacrosc89static-assertcompile-time-constant

Macro for use in expression while enforcing its arguments to be compile time constants


I am looking for a way to #define a macro that enforces its arguments to be compile time constants, and at the same time can be used in an expression. The method should be working under C90 and be upward compatible - if possible also portable for the different C++ variants. Also a 0-footprint to memory is preferable.

Consider a compile-time minimum macro as an example. The behavior should be:

 #define CT_MIN(CTC_A, CTC B) <<<MAGIC>>>

 int a = 1;
 int b = a + CT_MIN(1,4); /* OK */
 int c = a + CT_MIN(a,4); /* COMPILER DIAGNOSTIC */

To provoke an compile diagnostic, in general I could emulate a static assert with something like:

 typedef char ct_check_is_ct_constant[(CTC_A != 0) + 1];

which wouldn't compile (or will raise some diagnostic at least) if anything other than a compile time constant (number) is used for CTC_A; but if CTC_A is an number, it will always be successful (given some care with scopes). But this assert would only work within a multi-statement macro and so would not be possible to use as part of an expression.

I would guess, something in the lines of:

  #define CT_MIN(CTC_A, CTC_B)                   \
         ( <expr check CTC_A>,                   \
           <expr check CTC_B>,                   \
           (CTC_A) < (CTC_B))? (CTC_A) : (CTC_B) \
         )

But I have no idea how the expressions must look like, and if such thing exists.

Any Ideas?

Background:

I have a huge amount of constants from not too much reliable sources via an costumized header. These constants strongly parametrize my code. I want to implement a zero footprint checks during preprocessor and compile time for this constants, to check both my assumptions, the environment, and my skills to write a correct code generator.


Solution

  • You can use sizeof applied to an anonymous struct with a single field whose type is a locally defined enum whose values must be constant integer expressions:

    #define CT_MIN(CTC_A, CTC_B)                                               \
        ( sizeof(struct { enum { must_be_constant_expression = CTC_A } x; }),  \
          sizeof(struct { enum { must_be_constant_expression = CTC_B } x; }),  \
          (CTC_A) < (CTC_B) ? (CTC_A) : (CTC_B)                                )
    

    The error messages produced by clang is very explicit:

    enumassert.c:32:24: error: expression is not an integer constant expression
        int c = a + CT_MIN(a,4); /* COMPILER DIAGNOSTIC */
                           ^
    enumassert.c:17:60: note: expanded from macro 'CT_MIN'
        ( sizeof(struct { enum { must_be_constant_expression = CTC_A } x; }),  \
                                                               ^
    

    This solution does not seem to add any symbol to the name space. Furthermore, if you need to handle non integer types, at the cost of a slightly less precise error message, you can add a cast like this:

    #define CT_MIN(CTC_A, CTC_B)                                               \
        ( sizeof(struct { enum { must_be_constant_expression = (int)(CTC_A) } x; }),  \
          sizeof(struct { enum { must_be_constant_expression = (int)(CTC_B) } x; }),  \
          (CTC_A) < (CTC_B) ? (CTC_A) : (CTC_B)                                )