This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.
std::indirect's operator== still does not support incomplete typesSection: 20.4.1.8 [indirect.relops], 20.4.1.9 [indirect.comp.with.t] Status: New Submitter: Hewill Kang Opened: 2025-08-24 Last modified: 2025-10-14
Priority: 2
View all issues with New status.
Discussion:
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.
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 22.10.6.1 [refwrap.general].
[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."
Proposed resolution:
This wording is relative to N5014.
[Drafting note:: We introduce the exposition-only function
Please note that the seemingly unresolvedFUNbelow to mimic the implicit conversion tobool. As a drive-by effect this helps us simplifying (and clarifying, see LWG 484(i)) the existing Mandates element.Tin therequiresexpression below names the first template parameter of theindirectclass template. ]
Modify 20.4.1.8 [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
FUNdenote the exposition-only functionbool FUN(bool) noexcept;-1- Mandates: The expression
-2- Returns: IfFUN(*lhs == *rhs)is well-formedand its result is convertible to.boollhsis valueless orrhsis valueless,lhs.valueless_after_move() == rhs.valueless_after_move(); otherwise*lhs == *rhs. -?- Remarks: The exception specification is equivalent to:requires (const T& lhs, const U& rhs) { { FUN(lhs == rhs) } noexcept; }
Modify 20.4.1.9 [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
FUNdenote the exposition-only functionbool FUN(bool) noexcept;-1- Mandates: The expression
-2- Returns: IfFUN(*lhs == rhs)is well-formedand its result is convertible to.boollhsis valueless,false; otherwise*lhs == rhs. -?- Remarks: The exception specification is equivalent to:requires (const T& lhs, const U& rhs) { { FUN(lhs == rhs) } noexcept; }