Title
tuple relational operators have confused friendships
Status
new
Section
[tuple.rel]
Submitter
Corentin Jabot

Created on 2023-02-08.00:00:00 last changed 14 months ago

Messages

Date: 2023-03-22.22:40:39

Proposed resolution:

This wording is relative to N4928.

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

    namespace std {
      […]
      // [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 [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;
    
        // [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&);
    
        // [tuple.swap], tuple swap
        constexpr void swap(tuple&) noexcept(see below);
        constexpr void swap(const tuple&) const noexcept(see below);
      };
    }
    
  3. Modify [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> ([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 ([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> ([range.utility.helpers]) is true.

    -7- Effects: […]

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

Date: 2023-03-15.00:00:00

[ 2023-03-22; Reflector poll ]

Set priority to 3 after reflector poll.

Date: 2023-03-15.00:00:00

[ 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 [hidden.friends] apply.

The reusage of the exposition-only concept different-from is already existing practice in various places of [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.

Date: 2023-02-15.00:00:00

[ 2023-02-18; Daniel provides wording ]

This wording is relative to N4928.

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

    namespace std {
      […]
      // [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 [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;
    
        // [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&);
    
        // [tuple.swap], tuple swap
        constexpr void swap(tuple&) noexcept(see below);
        constexpr void swap(const tuple&) const noexcept(see below);
      };
    }
    
  3. Modify [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 ([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 ([basic.lookup.argdep]) only.

Date: 2023-02-08.00:00:00

In [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&);
};
History
Date User Action Args
2023-03-22 22:40:39adminsetmessages: + msg13472
2023-03-05 15:28:47adminsetmessages: + msg13450
2023-02-18 18:16:18adminsetmessages: + msg13409
2023-02-18 18:16:18adminsetmessages: + msg13408
2023-02-08 00:00:00admincreate