Created on 2025-08-24.00:00:00 last changed 1 month ago
Proposed resolution:
This wording is relative to N5014.
[Drafting note:: We introduce the exposition-only function FUN below to mimic the implicit conversion to `bool`. As a drive-by effect this helps us simplifying (and clarifying, see LWG 484) the existing Mandates element.
Please note that the seemingly unresolved `T` in the `requires` expression below names the first template parameter of the `indirect` class template. ]
Modify [indirect.relops] as indicated:
template<class U, class AA> constexpr bool operator==(const indirect& lhs, const indirect<U, AA>& rhs) noexcept(noexcept(*lhs == *rhs)see below);-?- Let FUN denote the exposition-only function
bool FUN(bool) noexcept;-1- Mandates: The expression FUN(*lhs == *rhs) is well-formed
-2- Returns: If `lhs` is valueless or `rhs` is valueless, `lhs.valueless_after_move() == rhs.valueless_after_move()`; otherwise `*lhs == *rhs`. -?- Remarks: The exception specification is equivalent to:and its result is convertible to `bool`.requires (const T& lhs, const U& rhs) { { FUN(lhs == rhs) } noexcept; }
Modify [indirect.comp.with.t] as indicated:
template<class U> constexpr bool operator==(const indirect& lhs, const U& rhs) noexcept(noexcept(*lhs == rhs)see below);-?- Let FUN denote the exposition-only function
bool FUN(bool) noexcept;-1- Mandates: The expression FUN(*lhs == rhs) is well-formed
-2- Returns: If `lhs` is valueless, `false`; otherwise `*lhs == rhs`. -?- Remarks: The exception specification is equivalent to:and its result is convertible to `bool`.requires (const T& lhs, const U& rhs) { { FUN(lhs == rhs) } noexcept; }
[ 2025-10-14; Reflector poll ]
Set priority to 2 after reflector poll.
Even if we don't want to support incomplete types, we still need to fix the noexcept-specifier to consider whether the conversion to `bool` throws, e.g. noexcept(noexcept(bool(*lhs == *rhs))).
"The proposed resolution may result in the noexcept operator giving different results when evaluated in different translation units where the type `T` of `indirect` was incomplete or not. Ill-formed seems safer than inconsistent."
`std::indirect`'s `operator== intentionally uses Mandates instead of Constraints to support incomplete types. However, its function signature has the following `noexcept` specification:
template<class U, class AA>
constexpr bool operator==(const indirect& lhs, const indirect<U, AA>& rhs)
noexcept(noexcept(*lhs == *rhs));
That is, we check whether the expression `*lhs == *rhs` throws, which unfortunately leads to the following hard error:
struct Incomplete;
static_assert(std::equality_comparable<std::indirect<Incomplete>>);
// hard error, no match for 'operator==' (operand types are 'const Incomplete' and 'const Incomplete')
This makes `operator==` not SFINAE-friendly for incomplete types, which defeats the purpose.
Also, checking `noexcept(*lhs == *rhs)` seems insufficient because the result of `*lhs == *rhs` might still throw during conversion to `bool`.We could add a note similar to [refwrap.general].
| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2025-10-14 21:30:14 | admin | set | messages: + msg15171 |
| 2025-08-24 17:30:44 | admin | set | messages: + msg14956 |
| 2025-08-24 00:00:00 | admin | create | |