1

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Mark A.
  • 579
  • 4
  • 13
  • Watch out for VLA types. I think you might be able to define a struct type with a bitfield whose size is defined in terms of the macro argument, although I wouldn't trust myself to be aware of all the caveats and get it completely right. – user2357112 Jan 30 '16 at 21:40
  • @user2357112 :-) I am not totally sure how VLA types can be used for achieving a MIN macro, that will fail at compile time, if its arguments are not compile-time constants. – Mark A. Jan 30 '16 at 21:52
  • I'm saying to make sure you don't accidentally declare a VLA type thinking that array lengths are always compile-time constant. – user2357112 Jan 30 '16 at 21:57
  • Ah, I see. Thanks. I am not totally aware of the VLA features (still stucking with C89 somehow). Can you besides the answer provide an static assert that will not become an VLA? – Mark A. Jan 30 '16 at 22:00
  • It has to be standard C89? For instance, gcc has extensions that would let you achieve this. – Nate Eldredge Jan 30 '16 at 22:47
  • @user2357112: combining different approaches, I found a c89 solution that fits the requirements. – chqrlie Jan 30 '16 at 23:32

2 Answers2

2

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)                                )
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • looks good really. Does this approach add something to the name space, the macro is used in? – Mark A. Jan 31 '16 at 09:14
  • Also how about float constants - will something like: ´sizeof(struct {enum {XXX = (int) (CTC_A)} x; })´ do the trick? Overflows might also then cause problems, right? Would an unsigned cast - to say (unsigned int) be well defined on big floats? – Mark A. Jan 31 '16 at 09:17
  • @MarkA.: I don't think it even adds the symbol `must_be_constant_expression` to the namespace because it is scoped inside the `struct` and redefined just below with a different value without a warning. – chqrlie Jan 31 '16 at 19:26
  • ok. thanks! If you can confirm that the addon of your solution given by answer does the job (and maybe more generally), you might want to update your answer, then I would like to accept yours. – Mark A. Jan 31 '16 at 19:34
  • A cast from big floating point variables, I think is not enough because it can't handle overflows. I used the ! Operator to generate a small value from any number - hopefully. – Mark A. Jan 31 '16 at 19:44
  • @MarkA.: large floating point values are not a problem if you first multiply by `0`, but doing so generates a warning with clang, just like your idea with the `!` operator :( – chqrlie Jan 31 '16 at 19:46
  • Multiplying with 0. Clever. Please update again! :-) – Mark A. Jan 31 '16 at 19:48
  • @MarkA.: sorry, multiplying by `0` or `0.0` generates a warning even for small floats. There are gcc builtins that you can use for this purpose, they might be supported by clang but not by other compilers, but it is not a real problem since you only use this for linting: you really only need one supported platform. – chqrlie Jan 31 '16 at 19:53
  • Ok. So the question is: Why? ... I will tomorrow try to find the reason by the lines of the standard - and if it works with our tool chain. If you like to search with me, you can add citations from the standards if you find a reason. – Mark A. Jan 31 '16 at 19:57
  • Ok. Found the reason in the standard: ANSI/ISO 9899:1990 6.5.2.2 Enum Specifiers: "Constraints: [...] an enumeration constant shall be an integral constant expression [...]" and 6.4 Constant expressions: " [...] An integral constant expression [...] shall only have operands that are [... stuff ...] sizeof expressions and floating constants that are the immediate operands of casts. [...]" – Mark A. Feb 01 '16 at 07:33
1

For evaluation of both floating point and integer compile-time constants I think I can use @chqrlie's solution and upgrade it a little.

The expression for the check if the parameter is a compile time constant might be:

  sizeof(struct { enum { must_be_constant_expression = (int) (!(CTC_A))} x; })

(I am not sure if the unary ! gives yield an int or something of type of CTC_A)

This should handle most of anything, that a MIN operation can be reasonably done for at compile time.

The complete macro then is:

 #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)                                          \
     )
Mark A.
  • 579
  • 4
  • 13
  • The `!` operator indeed evaluates to an `int` with value `0` or `1`. This solution should work just like the simple cast, for for some reason `clang` complains that it is not a compiler time constant if the argument is a double, claiming that folding it to a constant is a GNU extension. I don't understand why, but it makes this solution unusable. – chqrlie Jan 31 '16 at 19:42
  • @chqrlie See my comment to your updated answer. ... Is this a compiler bug? I don't a reason in the standard... – Mark A. Jan 31 '16 at 19:45
  • Ok. Found the reason in the standard: ANSI/ISO 9899:1990 6.5.2.2 Enum Specifiers: "Constraints: [...] an enumeration constant shall be an integral constant expression [...]" and 6.4 Constant expressions: " [...] An integral constant expression [...] shall only have operands that are [... stuff ...] sizeof expressions and floating constants that are the immediate operands of casts. [...]" – Mark A. Feb 01 '16 at 07:33
  • 1
    applying `sizeof` to the argument defeats the purpose of checking if it is a constant expression. `sizeof(expr)` is a constant expression even if `expr` has side effects... – chqrlie Feb 01 '16 at 07:38
  • @chqrlie Correct :-) Couldn't delete fast enough my overexciting comment. But can we maybe instrumentalize the initializer list of a struct or array together with this pattern? In initializer lists we can use any constant expressions (C90) -- Well then again we might loose upward compatibility -- can struct initizializers be non-constant expressions > C90 standards? – Mark A. Feb 01 '16 at 12:49