3882. tuple relational operators have confused friendships

Section: 22.4.9 [tuple.rel] Status: New Submitter: Corentin Jabot Opened: 2023-02-08 Last modified: 2023-03-22

Priority: 3

View other active issues in [tuple.rel].

View all other issues in [tuple.rel].

View all issues with New status.

Discussion:

In 22.4.9 [tuple.rel]:

template<class... TTypes, tuple-like UTuple>
  constexpr bool operator==(const tuple<TTypes...>& t, const UTuple& u);

Is defined as a non-member non-friend function that "is to be found via argument-dependent lookup only."

The intent is that it should be defined as a hidden friend in tuple.

The current specification is confusing as to which class should contain that hidden friend, or how to otherwise implement that adl only restriction. (An hostile reading may consider it to be a hidden friend of UTuple), and does not follow the guidance of P1601 "Recommendations for Specifying ``Hidden Friends''".

We should consider making these operator== and operator<=> overloads hidden friends of tuple, i.e.

std::tuple {
  template<class... TTypes, tuple-like UTuple>
  friend constexpr bool operator==(const tuple& t, const UTuple& u);
  template<class... TTypes, tuple-like UTuple>
  friend constexpr see below operator<=>(const tuple&, const UTuple&);
};

[2023-02-18; Daniel provides wording]

Previous resolution [SUPERSEDED]:

This wording is relative to N4928.

  1. Modify 22.4.2 [tuple.syn], header <tuple> synopsis, as indicated:

    namespace std {
      […]
      // 22.4.9 [tuple.rel], relational operators
      template<class... TTypes, class... UTypes>
        constexpr bool operator==(const tuple<TTypes...>&, const tuple<UTypes...>&);
      template<class... TTypes, tuple-like UTuple>
        constexpr bool operator==(const tuple<TTypes...>&, const UTuple&);
      template<class... TTypes, class... UTypes>
        constexpr common_comparison_category_t<synth-three-way-result<TTypes, UTypes>...>
          operator<=>(const tuple<TTypes...>&, const tuple<UTypes...>&);
      template<class... TTypes, tuple-like UTuple>
        constexpr see below operator<=>(const tuple<TTypes...>&, const UTuple&);
      […]
    }
    
  2. Modify 22.4.4 [tuple.tuple], class template tuple synopsis, as indicated:

    namespace std {
      template<class... Types>
      class tuple {
      public:
        […]
    
        template<tuple-like UTuple>
          constexpr tuple& operator=(UTuple&&);
        template<tuple-like UTuple>
          constexpr const tuple& operator=(UTuple&&) const;
    
        // 22.4.9 [tuple.rel], relational operators
        template<tuple-like UTuple>
          friend constexpr bool operator==(const tuple&, const UTuple&);
        template<tuple-like UTuple>
          friend constexpr see below operator<=>(const tuple&, const UTuple&);
    
        // 22.4.4.4 [tuple.swap], tuple swap
        constexpr void swap(tuple&) noexcept(see below);
        constexpr void swap(const tuple&) const noexcept(see below);
      };
    }
    
  3. Modify 22.4.9 [tuple.rel] as indicated:

    template<class... TTypes, class... UTypes>
      constexpr bool operator==(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
    template<class... TTypes, tuple-like UTuple>
      friend constexpr bool operator==(const tuple<TTypes...>& t, const UTuple& u);
    

    -1- For the first overload let UTuple be tuple<UTypes...>. For the second overload let TTypes denote the pack Types.

    […]

    -5- Remarks:

    1. (5.1) — The elementary comparisons are performed in order from the zeroth index upwards. No comparisons or element accesses are performed after the first equality comparison that evaluates to false.

    2. (5.2) — The second overload is to be found via argument-dependent lookup (6.5.4 [basic.lookup.argdep]) only.

    template<class... TTypes, class... UTypes>
      constexpr common_comparison_category_t<synth-three-way-result<TTypes, UTypes>...>
        operator<=>(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
    template<class... TTypes, tuple-like UTuple>
      friend constexpr common_comparison_category_t<synth-three-way-result<TTypes, Elems>...> 
        operator<=>(const tuple<TTypes...>& t, const UTuple& u);
    

    -6- For the second overload, let TTypes denote the pack Types and Elems denotes the pack of types tuple_element_t<0, UTuple>, tuple_element_t<1, UTuple>, ... , tuple_element_t<tuple_size_v<UTuple> - 1, UTuple>.

    […]

    -8- Remarks: The second overload is to be found via argument-dependent lookup (6.5.4 [basic.lookup.argdep]) only.

[2023-03-05; Daniel comments and provides improved wording]

The revised wording ensures that no ambiguity exists between the overload candidates, furthermore the additional wording about being found via argument-dependent lookup only has been eliminated, because the general conventions of 16.4.6.6 [hidden.friends] apply.

The reusage of the exposition-only concept different-from is already existing practice in various places of 22.4 [tuple]. We have also existing wording practice where we have an extra Constraints: element added even though a prototype has already a language constraint as part of its signature.

[2023-03-22; Reflector poll]

Set priority to 3 after reflector poll.

Proposed resolution:

This wording is relative to N4928.

  1. Modify 22.4.2 [tuple.syn], header <tuple> synopsis, as indicated:

    namespace std {
      […]
      // 22.4.9 [tuple.rel], relational operators
      template<class... TTypes, class... UTypes>
        constexpr bool operator==(const tuple<TTypes...>&, const tuple<UTypes...>&);
      template<class... TTypes, tuple-like UTuple>
        constexpr bool operator==(const tuple<TTypes...>&, const UTuple&);
      template<class... TTypes, class... UTypes>
        constexpr common_comparison_category_t<synth-three-way-result<TTypes, UTypes>...>
          operator<=>(const tuple<TTypes...>&, const tuple<UTypes...>&);
      template<class... TTypes, tuple-like UTuple>
        constexpr see below operator<=>(const tuple<TTypes...>&, const UTuple&);
      […]
    }
    
  2. Modify 22.4.4 [tuple.tuple], class template tuple synopsis, as indicated:

    namespace std {
      template<class... Types>
      class tuple {
      public:
        […]
    
        template<tuple-like UTuple>
          constexpr tuple& operator=(UTuple&&);
        template<tuple-like UTuple>
          constexpr const tuple& operator=(UTuple&&) const;
    
        // 22.4.9 [tuple.rel], relational operators
        template<tuple-like UTuple>
          friend constexpr bool operator==(const tuple&, const UTuple&);
        template<tuple-like UTuple>
          friend constexpr see below operator<=>(const tuple&, const UTuple&);
    
        // 22.4.4.4 [tuple.swap], tuple swap
        constexpr void swap(tuple&) noexcept(see below);
        constexpr void swap(const tuple&) const noexcept(see below);
      };
    }
    
  3. Modify 22.4.9 [tuple.rel] as indicated:

    template<class... TTypes, class... UTypes>
      constexpr bool operator==(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
    template<class... TTypes, tuple-like UTuple>
      friend constexpr bool operator==(const tuple<TTypes...>& t, const UTuple& u);
    

    -1- For the first overload let UTuple be tuple<UTypes...>. For the second overload let TTypes denote the pack Types.

    -?- Constraints: For the second overload, different-from<UTuple, tuple> (25.5.2 [range.utility.helpers]) is true.

    -2- Mandates: […]

    […]

    -5- Remarks:

    1. (5.1) — The elementary comparisons are performed in order from the zeroth index upwards. No comparisons or element accesses are performed after the first equality comparison that evaluates to false.

    2. (5.2) — The second overload is to be found via argument-dependent lookup (6.5.4 [basic.lookup.argdep]) only.

    template<class... TTypes, class... UTypes>
      constexpr common_comparison_category_t<synth-three-way-result<TTypes, UTypes>...>
        operator<=>(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
    template<class... TTypes, tuple-like UTuple>
      friend constexpr common_comparison_category_t<synth-three-way-result<TTypes, Elems>...> 
        operator<=>(const tuple<TTypes...>& t, const UTuple& u);
    

    -6- For the second overload, let TTypes denote the pack Types and Elems denotes the pack of types tuple_element_t<0, UTuple>, tuple_element_t<1, UTuple>, ... , tuple_element_t<tuple_size_v<UTuple> - 1, UTuple>.

    -?- Constraints: For the second overload, different-from<UTuple, tuple> (25.5.2 [range.utility.helpers]) is true.

    -7- Effects: […]

    -8- Remarks: The second overload is to be found via argument-dependent lookup (6.5.4 [basic.lookup.argdep]) only.