2485. get() should be overloaded for const tuple&&

Section: 22.4.8 [tuple.elem] Status: C++17 Submitter: Stephan T. Lavavej Opened: 2015-03-27 Last modified: 2017-07-30

Priority: 1

View all other issues in [tuple.elem].

View all issues with C++17 status.

Discussion:

const rvalues are weird, but they're part of the type system. Consider the following code:

#include <functional>
#include <string>
#include <tuple>

using namespace std; 

string str1() { return "one"; }
const string str2() { return "two"; }
tuple<string> tup3() { return make_tuple("three"); }
const tuple<string> tup4() { return make_tuple("four"); }

int main() {
  // cref(str1()); // BAD, properly rejected
  // cref(str2()); // BAD, properly rejected
  // cref(get<0>(tup3())); // BAD, properly rejected
  cref(get<0>(tup4())); // BAD, but improperly accepted!
}

As tuple is a fundamental building block (and the only convenient way to have variadic data members), it should not open a hole in the type system. get() should imitate 7.6.1.5 [expr.ref]'s rules for accessing data members. (This is especially true for pair, where both get<0>() and .first are available.)

While we're in the neighborhood, we can dramatically simplify the wording here. All we need to do is follow 22.4.8 [tuple.elem]/9's example of saying "Returns: A reference to STUFF", and allow implementers to figure out how to achieve that with the given return types.

[2015-05, Lenexa]

TP: for the existing overloads there's no change to the code, just descriptions?
STL: right.
JW: I love it
MC: in favor of moving to Ready and bringing up for vote on Friday
7 in favor, none opposed

Proposed resolution:

This wording is relative to N4296.

  1. Change 22.2 [utility]/2 "Header <utility> synopsis" as depicted:

    […]
    template<size_t I, class T1, class T2>
      constexpr tuple_element_t<I, pair<T1, T2>>&
        get(pair<T1, T2>&) noexcept;
    template<size_t I, class T1, class T2>
      constexpr tuple_element_t<I, pair<T1, T2>>&&
        get(pair<T1, T2>&&) noexcept;
    template<size_t I, class T1, class T2>
      constexpr const tuple_element_t<I, pair<T1, T2>>&
        get(const pair<T1, T2>&) noexcept;
    template<size_t I, class T1, class T2>
      constexpr const tuple_element_t<I, pair<T1, T2>>&&
        get(const pair<T1, T2>&&) noexcept;
    template <class T, class U>
      constexpr T& get(pair<T, U>& p) noexcept;
    template <class T, class U>
      constexpr const T& get(const pair<T, U>& p) noexcept;
    template <class T, class U>
      constexpr T&& get(pair<T, U>&& p) noexcept;
    template <class T, class U>
      constexpr const T&& get(const pair<T, U>&& p) noexcept;
    template <class T, class U>
      constexpr T& get(pair<U, T>& p) noexcept;
    template <class T, class U>
      constexpr const T& get(const pair<U, T>& p) noexcept;
    template <class T, class U>
      constexpr T&& get(pair<U, T>&& p) noexcept;
    template <class T, class U>
      constexpr const T&& get(const pair<U, T>&& p) noexcept;
    […]
    
  2. Change 22.4.1 [tuple.general]/2 "Header <tuple> synopsis" as depicted:

    […]
    // 20.4.2.6, element access:
    template <size_t I, class... Types>
    constexpr tuple_element_t<I, tuple<Types...>>&
    get(tuple<Types...>&) noexcept;
    template <size_t I, class... Types>
    constexpr tuple_element_t<I, tuple<Types...>>&&
    get(tuple<Types...>&&) noexcept;
    template <size_t I, class... Types>
    constexpr const tuple_element_t<I, tuple<Types...>>&
    get(const tuple<Types...>&) noexcept;
    template <size_t I, class... Types>
    constexpr const tuple_element_t<I, tuple<Types...>>&&
    get(const tuple<Types...>&&) noexcept;
    template <class T, class... Types>
    constexpr T& get(tuple<Types...>& t) noexcept;
    template <class T, class... Types>
    constexpr T&& get(tuple<Types...>&& t) noexcept;
    template <class T, class... Types>
    constexpr const T& get(const tuple<Types...>& t) noexcept;
    template <class T, class... Types>
    constexpr const T&& get(const tuple<Types...>&& t) noexcept;
    […]
    
  3. Change 23.3.1 [sequences.general]/2 "Header <array> synopsis" as depicted:

    […]
    template <size_t I, class T, size_t N>
    constexpr T& get(array<T, N>&) noexcept;
    template <size_t I, class T, size_t N>
    constexpr T&& get(array<T, N>&&) noexcept;
    template <size_t I, class T, size_t N>
    constexpr const T& get(const array<T, N>&) noexcept;
    template <size_t I, class T, size_t N>
    constexpr const T&& get(const array<T, N>&&) noexcept;
    […]
    
  4. Change 22.3.4 [pair.astuple] as depicted:

    template<size_t I, class T1, class T2>
    constexpr tuple_element_t<I, pair<T1, T2>>&
    get(pair<T1, T2>& p) noexcept;
    template<size_t I, class T1, class T2>
    constexpr const tuple_element_t<I, pair<T1, T2>>&
    get(const pair<T1, T2>& p) noexcept;
    

    -3- Returns: If I == 0 returns p.first; if I == 1 returns p.second; otherwise the program is ill-formed.

    template<size_t I, class T1, class T2>
    constexpr tuple_element_t<I, pair<T1, T2>>&&
    get(pair<T1, T2>&& p) noexcept;
    template<size_t I, class T1, class T2>
    constexpr const tuple_element_t<I, pair<T1, T2>>&&
    get(const pair<T1, T2>&& p) noexcept;
    

    -4- Returns: If I == 0 returns a reference to std::forward<T1&&>(p.first); if I == 1 returns a reference to std::forward<T2&&>(p.second); otherwise the program is ill-formed.

    template <class T, class U>
    constexpr T& get(pair<T, U>& p) noexcept;
    template <class T, class U>
    constexpr const T& get(const pair<T, U>& p) noexcept;
    

    -5- Requires: T and U are distinct types. Otherwise, the program is ill-formed.

    -6- Returns: get<0>(p);

    template <class T, class U>
    constexpr T&& get(pair<T, U>&& p) noexcept;
    template <class T, class U>
    constexpr const T&& get(const pair<T, U>&& p) noexcept;
    

    -7- Requires: T and U are distinct types. Otherwise, the program is ill-formed.

    -8- Returns: A reference to p.first.get<0>(std::move(p));

    template <class T, class U>
    constexpr T& get(pair<U, T>& p) noexcept;
    template <class T, class U>
    constexpr const T& get(const pair<U, T>& p) noexcept;
    

    -9- Requires: T and U are distinct types. Otherwise, the program is ill-formed.

    -10- Returns: get<1>(p);

    template <class T, class U>
    constexpr T&& get(pair<U, T>&& p) noexcept;
    template <class T, class U>
    constexpr const T&& get(const pair<U, T>&& p) noexcept;
    

    -11- Requires: T and U are distinct types. Otherwise, the program is ill-formed.

    -12- Returns: A reference to p.second.get<1>(std::move(p));

  5. Change 22.4.8 [tuple.elem] as depicted:

    template <size_t I, class... Types>
      constexpr tuple_element_t<I, tuple<Types...> >& get(tuple<Types...>& t) noexcept;
    

    -1- Requires: I < sizeof...(Types). The program is ill-formed if I is out of bounds.

    -2- Returns: A reference to the Ith element of t, where indexing is zero-based.

    template <size_t I, class... Types>
      constexpr tuple_element_t<I, tuple<Types...> >&& get(tuple<Types...>&& t) noexcept; // Note A
    

    -3- Effects: Equivalent to return std::forward<typename tuple_element<I, tuple<Types...> >::type&&>(get<I>(t));

    -4- Note: if a T in Types is some reference type X&, the return type is X&, not X&&. However, if the element type is a non-reference type T, the return type is T&&.

    template <size_t I, class... Types>
      constexpr tuple_element_t<I, tuple<Types...> > const& get(const tuple<Types...>& t) noexcept; // Note B
    template <size_t I, class... Types>
      constexpr const tuple_element_t<I, tuple<Types...> >&& get(const tuple<Types...>&& t) noexcept;  
    

    -5- Requires: I < sizeof...(Types). The program is ill-formed if I is out of bounds.

    -6- Returns: A const reference to the Ith element of t, where indexing is zero-based.

    -?- [Note A: if a T in Types is some reference type X&, the return type is X&, not X&&. However, if the element type is a non-reference type T, the return type is T&&. — end note]

    -7- [Note B: Constness is shallow. If a T in Types is some reference type X&, the return type is X&, not const X&. However, if the element type is non-reference type T, the return type is const T&. This is consistent with how constness is defined to work for member variables of reference type. — end note]

    template <class T, class... Types>
      constexpr T& get(tuple<Types...>& t) noexcept;
    template <class T, class... Types>
      constexpr T&& get(tuple<Types...>&& t) noexcept;
    template <class T, class... Types>
      constexpr const T& get(const tuple<Types...>& t) noexcept;
    template <class T, class... Types>
      constexpr const T&& get(const tuple<Types...>&& t) noexcept;
    

    -8- Requires: The type T occurs exactly once in Types.... Otherwise, the program is ill-formed.

    -9- Returns: A reference to the element of t corresponding to the type T in Types....

    […]

  6. Change 23.3.3.7 [array.tuple] as depicted:

    template <size_t I, class T, size_t N>
      constexpr T& get(array<T, N>& a) noexcept;
    

    -3- Requires: I < N. The program is ill-formed if I is out of bounds.

    -4- Returns: A reference to the Ith element of a, where indexing is zero-based.

    template <size_t I, class T, size_t N>
      constexpr T&& get(array<T, N>&& a) noexcept;
    

    -5- Effects: Equivalent to return std::move(get<I>(a));

    template <size_t I, class T, size_t N>
      constexpr const T& get(const array<T, N>& a) noexcept;
    template <size_t I, class T, size_t N>
      constexpr const T&& get(const array<T, N>&& a) noexcept;
    

    -6- Requires: I < N. The program is ill-formed if I is out of bounds.

    -7- Returns: A const reference to the Ith element of a, where indexing is zero-based.