Viable user-defined conversions in converted constant expressions
7.7 [expr.const]
Mike Miller

Created on 2020-08-17.00:00:00 last changed 11 months ago


Date: 2021-02-15.00:00:00

Notes from the February, 2021 teleconference:

CWG agreed with the considerations in the December, 2020 note, feeling that the difference in treatment between integral constant expressions and a converted constant expression to a specific integral type is somewhat gratuitous. However, it was felt that code like that of the example was unlikely to occur often in real-world code.

Date: 2020-12-15.00:00:00

Additional note, December, 2020:

The clang behavior turns out to have been an oversight, corrected in the current version, so the example is now rejected by both compilers. However, it is unclear that this is desirable. In particular, given the example above, a can be used without error as a bit-field width, as an enumerator value, and as the operand of alignas. Presumably the difference between these integral constant expression contexts and an array bound is the fact that the target type is known to be size_t. However, both bit-field widths and alignas operands are also required to be non-negative. Furthermore, the definition of an “erroneous” array bound in [expr.new] paragraph 9 goes to awkward lengths to check for negative values as the result of user-defined conversions, which might argue in favor of reconsidering the converted constant expression treatment of array bounds.

Date: 2020-08-15.00:00:00

Notes from the August, 2020 teleconference:

No direction was established pending information about why the example is accepted by clang.

Date: 2020-08-17.00:00:00

Consider an example like the following:

  struct A {
    constexpr A(int i) : val(i) { }
    constexpr operator int() const { return val; }
    constexpr operator float() const { return val; }
    int val;
  constexpr A a = 42;
  int ary[a];

According to [dcl.array] paragraph 1, the array bound expression

shall be a converted constant expression of type std::size_t (7.7 [expr.const]).

The user-defined conversion to float would involve a floating-integral conversion (7.3.11 [conv.fpint]; however, such a conversion is not permitted by the list of acceptable conversions in 7.7 [expr.const] paragraph 10:

A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only

  • user-defined conversions,

  • lvalue-to-rvalue conversions (7.3.2 [conv.lval]),

  • array-to-pointer conversions (7.3.3 [conv.array]),

  • function-to-pointer conversions (7.3.4 [conv.func]),

  • qualification conversions (7.3.6 [conv.qual]),

  • integral promotions (7.3.7 [conv.prom]),

  • integral conversions (7.3.9 [conv.integral]) other than narrowing conversions (9.4.5 [dcl.init.list]),

  • null pointer conversions (7.3.12 [conv.ptr]) from std::nullptr_t,

  • null member pointer conversions (7.3.13 [conv.mem]) from std::nullptr_t, and

  • function pointer conversions (7.3.14 [conv.fctptr]),

and where the reference binding (if any) binds directly.

It is not clear whether this list is intended to restrict the set of viable user-defined conversions, and there is implementation divergence on this point: clang accepts the example above, while g++ rejects it, presumably on the basis of an ambiguous conversion.

Date User Action Args
2021-02-17 00:00:00adminsetmessages: + msg6495
2020-12-15 00:00:00adminsetmessages: + msg6267
2020-12-15 00:00:00adminsetmessages: + msg6266
2020-08-17 00:00:00admincreate