This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 116c. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2025-03-11


3000. Handling of cv-qualified class types in conditional operator

Section: 7.6.16  [expr.cond]     Status: open     Submitter: Brian Bi     Date: 2025-02-02

With issue 2321, the following example was supposed to be made valid:

   struct A {};
   struct B : A {};
   using T = const B;
   A a = true ? A() : T();

When reaching 7.6.16 [expr.cond] paragraph 6, the two operands have type A and const A, thus their types differ. However, the built-in candidates for the overload resolution specified in paragraph 6 do not handle class types.

Subclause 7.6.16 [expr.cond] paragraph 6 handles the following case, but should not handle differences in const-qualification.

  struct A { operator int(); };
  struct B { operator int(); };
  int n = true ? A() : B();

Furthermore, the current wording does not handle this case:

  int const **p;
  int *const *q;
  int const * const * const &r = cond ? p : q; // Could bind directly, but currently creates a temporary.

Possible resolution [SUPERSEDED]:

  1. Change in 7.6.16 [expr.cond] paragraph 4 as follows:

    Otherwise, if the second and third operand have different types and either at least one has (possibly cv-qualified) class type, or if both are glvalues of similar types (7.3.6 [conv.qual]) and of the same value category and the same type except for cv-qualification, an attempt is made to form an implicit conversion sequence (12.2.4.2 [over.best.ics]) from each of those operands to the a type of related to the other.

    [Note 2: Properties such as access, whether an operand is a bit-field, or whether a conversion function is deleted are ignored for that determination. —end note]

    Attempts are made to form an implicit conversion sequence from an operand expression E1 of type T1 to a target type related to the type T2 of the operand expression E2 as follows:
    • If E2 is an lvalue, the target type is “lvalue reference to T2 T3”, where T3 is the qualification-combined type of T2 and T1; but an implicit conversion sequence can only be formed only if the reference would bind directly (9.4.4 [dcl.init.ref]) to a glvalue.
    • If E2 is an xvalue, the target type is “rvalue reference to T2 T3”, where T3 is the qualification-combined type of T2 and T1; but an implicit conversion sequence can only be formed only if the reference would bind directly.
    • If E2 is a prvalue or if neither of the conversion sequences above can be formed and at least one of the operands has (possibly cv-qualified) class type:
      • if T1 and T2 are the same class type (ignoring cv-qualification) of similar class types:
        • if T2 is at least as cv-qualified as T1, the target type is T2,
        • otherwise, no conversion sequence is formed for this operand;
      • otherwise, if T2 is a base class of T1, the target type is cv1 T2, where cv1 denotes the cv-qualifiers of T1 the qualification-combined type of T2 and T1;
      • otherwise, the target type is the type that E2 would have after applying the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions.
    Using this process, it is determined whether an implicit conversion sequence can be formed from the second operand to the target type determined for the third operand, and vice versa, with the following outcome:
    • If both sequences can be formed, or one can be formed but it is the ambiguous conversion sequence, the program is ill-formed.
    • If no conversion sequence can be formed, the operands are left unchanged and further checking is performed as described below.
    • Otherwise, if exactly one conversion sequence can be formed, that conversion is applied to the chosen operand the combined target type is defined as the target type of that conversion.
    • Otherwise, if both conversion sequences can be formed and the respective target types are similar, the combined target type is defined as the qualification-combined type of the target types.
    • Otherwise, the program is ill-formed.
    If a combined target type is defined above, each operand is converted to the combined target type and the converted operand is operands are is used in place of the original operand operands for the remainder of this subclause. [Note 3: The conversion might can be ill-formed even if an implicit conversion sequence could can be formed. —end note]
  2. Change 7.6.16 [expr.cond] paragraph 6 as follows:

    Otherwise, the result is a prvalue. If the second and third operands do not have the same type and either at least one has (possibly cv-qualified) class type, overload resolution is used to determine the conversions (if any) to be applied to the operands (12.2.2.3 [over.match.oper], 12.5 [over.built]). If the overload resolution fails, the program is ill-formed. Otherwise, the conversions thus determined are applied, and the converted operands are used in place of the original operands for the remainder of this subclause.
  3. Append a row to the table in 12.2.2.3 [over.match.oper] paragraph 2 as follows:

    Subclause Expression As member function As non-member function
    12.5 [over.built] a ? b : c operator?:(true, b, c)
  4. Change in 12.2.2.3 [over.match.oper] paragraph 3 as follows:

    For a unary operator @ with an operand of type cv1 T1, and for a binary operator @ with a left operand of type cv1 T1 and a right operand of type cv2 T2, and for the conditional operator, four sets of candidate functions, designated member candidates, non-member candidates, built-in candidates, and rewritten candidates, are constructed as follows:

Possible resolution:

  1. Change in 7.6.16 [expr.cond] paragraph 4 as follows:

    Otherwise, if the second and third operand have different types and either at least one has (possibly cv-qualified) class type, or if both are glvalues of similar types (7.3.6 [conv.qual]) and of the same value category and the same type except for cv-qualification, an attempt is made to form an implicit conversion sequence (12.2.4.2 [over.best.ics]) from each of those operands to the a type of related to the other.

    [Note 2: Properties such as access, whether an operand is a bit-field, or whether a conversion function is deleted are ignored for that determination. —end note]

    Attempts are made to form an implicit conversion sequence from an operand expression E1 of type T1 to a target type related to the type T2 of the operand expression E2 as follows:
    • If E2 is an lvalue a glvalue, the target type is “lvalue reference to T2 T3”, where
      • the reference is an lvalue reference if E2 is an lvalue and an rvalue reference otherwise and
      • if T2 is reference-related to T1 (9.4.4 [dcl.init.ref]), T3 is such that "pointer to T3" is the qualification-combined type of "pointer to T2" and "pointer to T1", otherwise T3 is T2;
      but an implicit conversion sequence can only be formed only if the reference would bind directly to a glvalue.
    • If E2 is an xvalue, the target type is “rvalue reference to T2”, but an implicit conversion sequence can only be formed if the reference would bind directly.
    • If E2 is a prvalue or if neither of the conversion sequences sequence above can cannot be formed and at least one of the operands has (possibly cv-qualified) class type:
      • if T1 and T2 are the same class type (ignoring cv-qualification): T2 is reference-related to T1, the target type T3 is such that "pointer to T3" is the qualification-combined type of "pointer to T2" and "pointer to T1";
        • if T2 is at least as cv-qualified as T1, the target type is T2,
        • otherwise, no conversion sequence is formed for this operand;
      • otherwise, if T2 is a base class of T1, the target type is cv1 T2, where cv1 denotes the cv-qualifiers of T1;
      • otherwise, the target type is the type that E2 would have after applying the lvalue-to-rvalue (7.3.2 [conv.lval]), array-to-pointer (7.3.3 [conv.array]), and function-to-pointer (7.3.4 [conv.func]) standard conversions.
    Using this process, it is determined whether an implicit conversion sequence can be formed from the second operand to the target type determined for the third operand, and vice versa, with the following outcome:
    • If both sequences can be formed, or one can be formed but it is the ambiguous conversion sequence, the program is ill-formed.
    • If no conversion sequence can be formed, the operands are left unchanged and further checking is performed as described below.
    • Otherwise, if exactly one conversion sequence can be formed, that conversion is applied to the chosen operand the common target type is defined as the target type of that conversion.
    • Otherwise, if both conversion sequences can be formed and the target types are the same, the common target type is defined as that type.
    • Otherwise, if both conversion sequences can be formed and one of the target types is "reference to T" and the other is T for some type T, the common target type is defined as T.
    • Otherwise, the program is ill-formed.
    If a common target type is defined above, each operand is converted to the common target type and the converted operand is operands are is used in place of the original operand operands for the remainder of this subclause. [Note 3: The conversion might can be ill-formed even if an implicit conversion sequence could can be formed. —end note]
  2. Change 7.6.16 [expr.cond] paragraph 6 as follows:

    Otherwise, the result is a prvalue. If the second and third operands do not have the same type and either at least one has (possibly cv-qualified) class type, overload resolution is used to determine the conversions (if any) to be applied to the operands (12.2.2.3 [over.match.oper], 12.5 [over.built]). If the overload resolution fails, the program is ill-formed. Otherwise, the conversions thus determined are applied, and the converted operands are used in place of the original operands for the remainder of this subclause.
  3. Add an example at the end of 7.6.16 [expr.cond] as follows:

    template<class T>
    T f();
    
    using X0 = decltype(true ? f<int const **&>() : f<int *const *&>());  // X0 is "reference to const pointer to const pointer to const int"
    
    struct B {};
    using X1 = decltype(true ? f<const B&>() : f<volatile B&>());  // X1 is "lvalue reference to const volatile B"
    using X2 = decltype(true ? f<const B&>() : f<B>());      // X2 is const B
    
    struct C {
      operator B&() const;
    };
    using X3 = decltype(true ? f<const C>() : f<B&>());      // X3 is "lvalue reference to B"
    
    struct D : B {};
    using X4 = decltype(true ? f<const D&>() : f<B&>());       // X4 is const B&
    using X5 = decltype(true ? f<const D>() : f<B>());       // X5 is const B
    
  4. Add a new paragraph before 12.2.2.3 [over.match.oper] paragraph 5 as follows:

    For the conditional operator, overload resolution is performed only as specified in 7.6.16 [expr.cond]; the candidate functions for the expression a ? b : c are the built-in candidates for the hypothetical function call operator?:(true, b, c) (12.5 [over.built]).

    For the first parameter of the built-in assignment operators, only standard conversion sequences (12.2.4.2.2 [over.ics.scs]) are considered.

The resolution above also adresses issues 2023 and 2865; the resolution applied for the latter yields a const lvalue, not a prvalue.