The following code is rejected by GCC and Clang (godbolt link):
struct thing;
typedef enum {
THING_TYPE_A,
THING_TYPE_B,
} thing_type_t;
typedef struct thing_a {
int i;
} thing_a_t;
typedef struct thing_b {
struct thing const *t;
} thing_b_t;
typedef struct thing {
thing_type_t type;
union {
thing_a_t a;
thing_b_t b;
} t;
} thing_t;
thing_t const *get_thing(void) {
static const thing_t s_thing = {
.type = THING_TYPE_B,
.t = {
.b = {
.t = &(thing_t) { .type = THING_TYPE_A, .t = { .a = { .i = 234 } } }
}
},
};
return &s_thing;
}
cppreference's page on Compound Literals says:
The unnamed object to which the compound literal evaluates has static storage duration if the compound literal occurs at file scope and automatic storage duration if the compound literal occurs at block scope (in which case the object's lifetime ends at the end of the enclosing block).
I believe that explains the compile error; the anonymous thing_t
whose address is used to initialize s_thing.t.b.t
has automatic storage duration and is therefore not a compile-time constant. If s_thing
is moved to file-scope, both Clang and GCC accept it. (There is more discussion at this SO question)
It looks like C23 will expand this by allowing constexpr
to be specified inside the compound literal parentheses, which is a welcome improvement!
In the meantime, is there any way to achieve a declaration like s_thing
(that is, the initialization of a static const struct that contains a pointer to another constant variable) in pre-C23 at block scope, without having to explicitly declare the anonymous thing_t
as its own separate variable?
In the meantime, is there any way to achieve a declaration like
s_thing
(that is, the initialization of a static const struct that contains a pointer to another constant variable) in pre-C23 at block scope, without having to explicitly declare the anonymousthing_t
as its own separate variable?
No, you have effectively ruled out all the possibilities.
An initializer for an object with static storage duration may contain only constant expressions.
For an object or sub-object of pointer type, the corresponding initializer element, if any, must be specifically an address constant, which is either a null pointer constant, or an integer constant expression cast to pointer type, or a pointer to an object having static storage duration, or a pointer to a function.
the only objects with static storage duration but no associated identifier are the arrays to which string literals correspond and compound literals appearing at file scope.
And I don't think it's different in C23 constexpr
helps. Yes, you can use constexpr
in the declaration of a compound literal to get a "compound literal constant" of structure type, but as far as I can tell, that does not confer static storage duration on said object. And if the object doesn't have static storage duration then its address is not an address constant.
However, C23 does allow you to specify storage class static
for a compound literal appearing at block scope, and that has the effect one would expect: the compound literal has static storage duration. In that case, its address is an address constant, and can be used in the initializer of another object with static storage duration.