Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Eisenwave opened this issue Jun 8, 2024 · 8 comments · Fixed by cplusplus/draft#7458

Comments

@Eisenwave
Copy link
Collaborator

Eisenwave commented Jun 8, 2024

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
Copy link

t3nsor commented Jun 8, 2024

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

@frederick-vs-ja
Copy link

#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
Copy link
Member

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
Copy link

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
Copy link
Member

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
Copy link

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
Copy link
Member

See also #442

@jensmaurer
Copy link
Member

CWG2906

@jensmaurer jensmaurer changed the title [expr.cond], [expr.const] Lvalue-to-rvalue conversion of class types disqualifies constant expressions CWG2906 [expr.cond], [expr.const] Lvalue-to-rvalue conversion of class types disqualifies constant expressions Jun 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants