cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2906 [expr.cond], [expr.const] Lvalue-to-rvalue conversion of class types disqualifies constant expressions #550

Open Eisenwave opened 3 weeks ago

Eisenwave commented 3 weeks ago

Reference (section label): [expr.cond], [expr.const]

Issue description

In some situations, the lvalue-to-rvalue conversion mandated by [expr.cond] paragraph 7 can apply to class types. Such lvalue-to-rvalue conversions may not be constant expressions (see [expr.const] paragraph 5, bullet 9) even though they result in a call to the copy constructor ([conv.lval] paragraph 3, bullet 2), and calling the copy constructor directly would be a constant expression.

struct S {};
S a;
constexpr S b = a;                // OK, call to implicitly-declared copy constructor
S c           = false ? S{} : a;  // OK, call to copy constructor through lvalue-to-rvalue conversion of 'a'
constexpr S d = false ? S{} : a;  // error: lvalue-to-rvalue conversion of 'a' is not a constant expression

None of MSVC, GCC, or Clang implement this behavior and permit constexpr S d = false ? S{} : a; instead.

Suggested resolution

The use of lvalue-to-rvalue conversion in [expr.cond] may be eliminated for class types.

Alternatively, update [expr.const] to permit this case.

t3nsor commented 3 weeks ago

Note: lvalue-to-rvalue conversions of class types also occur in https://eel.is/c++draft/expr.call#11

frederick-vs-ja commented 3 weeks ago

215 is related.

Note that implementations also accept this example:

struct S {
    S() = default;

    template<class = void>
    constexpr S(const volatile S&) noexcept {}
    template<class = void>
    constexpr S(const volatile S&&) noexcept {}
};

constexpr volatile S s_volatile{};
constexpr auto s_copy = []{ return s_volatile; }();

It seems that we should restrict [expr.const] p5.9 to scalar types other than cv std::nullptr_t.

  • an lvalue-to-rvalue conversion applied to a glvalue of a scalar type other than cv std::nullptr_t, unless ~it is applied to~ the glvalue is of a non-volatile type and refers to
    • ~a non-volatile glvalue that refers to~ an object that is usable in constant expressions, or
    • ~a non-volatile glvalue of literal type that refers to~ a non-volatile object whose lifetime began within the evaluation of E;

Moreover, [expr.const] p5.11 seemingly imposes unintend restrictions on empty classes and cv std::nullptr_t. Perhaps we should modify [expr.const] p5.11 as indicated.

  • an lvalue-to-rvalue conversion that ~is applied to an object with~ produces an indeterminate or erroneous value;
jensmaurer commented 3 weeks ago

Carving out an exception for nullptr is fine; we should never attempt to access the "object" (possibly with indeterminate or erroneous value) stored in a variable of type nullptr_t.

I would prefer reducing lvalue-to-rvalue conversions of class type.

For the "volatile" example, could you please point to the place where we do the lvalue-to-rvalue conversion here? [stmt.return] expressly does a copy-initialization, not an lvalue-to-rvalue conversion, so I'm not seeing why we need to adjust [expr.const] for that.

frederick-vs-ja commented 2 weeks ago

For the "volatile" example, could you please point to the place where we do the lvalue-to-rvalue conversion here?

As pointed above, [expr.call] p11 requires the lvalue-to-rvalue conversion.

struct S {
    S() = default;

    template<class = void>
    constexpr S(const volatile S&) noexcept {}
    template<class = void>
    constexpr S(const volatile S&&) noexcept {}
};

constexpr volatile S s_volatile{};

constexpr bool varfun(...) { return true; }
static_assert(varfun(s_volatile));

This example is rejected by GCC and accepted by Clang.

jensmaurer commented 2 weeks ago

That was not your original example. For the "s_copy" example, you claimed lvalue-to-rvalue conversion was necessary. I was asking where/when that is prescribed. You've just changed the example to switch to [expr.call] p11, which is known-dodgy of sorts.

frederick-vs-ja commented 2 weeks ago

That was not your original example. For the "s_copy" example, you claimed lvalue-to-rvalue conversion was necessary.

Oh, I'm sorry that the original example was wrong for lvalue-to-rvalue conversion. I originally thought that it was equivalent to lvalue-to-rvalue conversion and the requirements were the same.

jensmaurer commented 2 weeks ago

See also #442

jensmaurer commented 2 weeks ago

CWG2906