Open Eisenwave opened 3 weeks ago
Note: lvalue-to-rvalue conversions of class types also occur in https://eel.is/c++draft/expr.call#11
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;
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.
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.
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.
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.
See also #442
CWG2906
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.
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.