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.

4325. std::indirect's operator== still does not support incomplete types

Section: 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-08-24

Priority: Not Prioritized

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.

Also, checking noexcept(*lhs == *rhs) seems insufficient because the result of *lhs == *rhs might still throw during conversion to bool.

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(i)) 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. ]

  1. 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 FUN denote the exposition-only function

    bool FUN(bool) noexcept;
    

    -1- Mandates: The expression FUN(*lhs == *rhs) is well-formed and its result is convertible to bool.

    -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:

    requires (const T& lhs, const U& rhs) { { FUN(lhs == rhs) } noexcept; }
    
  2. 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 FUN denote the exposition-only function

    bool FUN(bool) noexcept;
    

    -1- Mandates: The expression FUN(*lhs == rhs) is well-formed and its result is convertible to bool.

    -2- Returns: If lhs is valueless, false; otherwise *lhs == rhs.

    -?- Remarks: The exception specification is equivalent to:

    requires (const T& lhs, const U& rhs) { { FUN(lhs == rhs) } noexcept; }