clanguage-lawyerconditional-operatorincomplete-type

Can the second and third operands of conditional operator have incomplete object types?


Per C11 (as well as per C2x) I understand that the second and third operands of conditional operator can have incomplete object types. Is that correct?

Code:

struct x y;
void f(void) { 1 ? y : y; }

Invocations:

$ clang t0.c -std=c11 -pedantic -Wall -Wextra
t0.c:2:24: warning: expression result unused [-Wunused-value]

$ gcc t0.c -std=c11 -pedantic -Wall -Wextra
t0.c: In function 'f':
t0.c:2:20: error: 'y' has an incomplete type 'struct x'
    2 | void f(void) { 1 ? y : y; }
      |                    ^
t0.c:2:24: error: 'y' has an incomplete type 'struct x'
    2 | void f(void) { 1 ? y : y; }

$ cl t0.c /std:c11
t0.c(1): error C2079: 'y' uses undefined struct 'x'

$ icc t0.c -std=c11 -pedantic -Wall -Wextra
t0.c(2): error: incomplete type is not allowed
  void f(void) { 1 ? y : y; }
                     ^
t0.c(2): error: incomplete type is not allowed
  void f(void) { 1 ? y : y; }
                         ^

Solution

  • Apple Clang 11.0.0 accepts this code without complaint, with -c -Wmost -Werror -std=c18 -pedantic:

    extern struct x y;
    void f(void) { (void) (1 ? y : y); }
    

    This is curious because the type of y is incomplete when f is being analyzed, so it implies the compiler analyzes the code sufficiently to determine the result of the conditional operator is not used and so the incompleteness of its operands is irrelevant.

    In any case, C 2018 6.3.2.1 2 says:

    Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion… If the lvalue has an incomplete type and does not have array type, the behavior is undefined.

    This is not in a constraints paragraph, so a C implementation is not required to diagnose it.

    However, the rule is puzzling. Lvalue conversion nominally occurs during program execution, since it needs the values stored in the bytes of an object. However, whether a type is complete or incomplete is a translation-time property; C 2018 6.2.5 1 says “… At various points within a translation unit an object type may be incomplete (lacking sufficient information to determine the size of objects of that type) or complete (having sufficient information).” If struct x { int i; } appeared after f, would the operands of the conditional operator be complete (because the type was completed before execution and hence before lvalue conversion occurred) or incomplete (because the type was incomplete when f was analyzed)?

    In any case, given that the operand type is incomplete, the behavior is not defined by the C standard. A compiler is free to compile it or to reject it. (Since the behavior is not defined, if the compiler does compile it, it is allowed to compile it into any code.)