Search code examples
cgccconditional-operatorcompile-timecompound-literals

Why can GCC not handle compile-time evaluation of compound literal in a ternary with sizeof as a condition inside a struct initializer?


The following code:

struct Int {
    int i;
};
const struct Int i = {sizeof(int) ? (int){1} : 0};

Results in:

initializer element is not constant

(Live demo GCC)

Even though those statements are at file scope and thus compound literals should be considered constants.

However, if:

  • I use Clang instead; OR
  • I replace the sizeof(int) with a 0; OR
  • I replace the (int){1} with a 1; OR
  • The expression is used to initialize an int directly instead of an int structure member like so: int i = sizeof(int) ? (int){1} : 0;

The code will compile just fine.

This raises a few questions:

  • Does standard C permit the use of sizeof() statements being used with ternaries to initialize one global structure variable member to one of 2 values, at least one of which is a compound literal, all at compile time?
  • Why does GCC raise an error if all 3 of the above requirements are met, but if only any 2 or 1 are met, the code compiles fine?

Solution

  • Before C23, there was a lot of arguing in gcc and clang bug reports etc, regarding what an integer constant expression consisted of. clang always had the behavior of "anything and its mother is a constant expression", whereas gcc has been more restrictive, until later versions where gcc suddenly flipped behavior even though the standard had not been changed here in ages. All of this was caused by an unhelpful, muddy paragraph in C17 (and earlier standards) 6.6 §10:

    An implementation may accept other forms of constant expressions.

    This is the only part of the chapter 6.6 leaving room for interpretation and allowing code like yours. This means that integer constant expressions have shaky support in both gcc and clang, code using "exotic" forms of constant expressions such as compound literals or const int x = some_other_const; is simply non-portable. Most other compilers in the market will give a diagnostic message about non-conforming C.


    Until C17 an integer constant expression was defined as follows (C17 6.6 §6):

    An integer constant expression shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, _Alignof expressions, and floating constants that are the immediate operands of casts.

    In upcoming C23 this changes (C23 N3096 draft 6.6. §8 emphasis mine):

    An integer constant expression shall have integer type and shall only have operands that are integer constants, named and compound literal constants of integer type, character constants, ...

    Where "named constant" is a new term which among other things covers this:

    declared with storage-class specifier constexpr and has an object type,

    So C23 will allow (constexpr int){1} compound literals and we may also portably do constexpr int x = 1; const int y=x;