June 24th, 2020

Mundane std::tuple tricks: Selecting via an index sequence, part 2

Last time, we developed the select_tuple function which takes a tuple and an index sequence and produces a new tuple that selects the elements based on the index sequence. Here’s what we had:

// Don't use this; see discussion.
template<typename Tuple, std::size_t... Ints>
auto select_tuple(Tuple&& tuple, std::index_sequence<Ints...>)
{
 return std::make_tuple(
    std::get<Ints>(std::forward<Tuple>(tuple))...);
}

The idea is that you can do something like

std::tuple<int, char, float> t{ 1, 'x', 2.0 };
auto t2 = select_tuple(t, std::index_sequence<0, 2>{});

and the result is that t2 is a std::tuple<int, float>{ 1, 2.0 }.

But there’s a problem with this function.

Here’s a riddle: When does std::make_tuple<T>() return something that isn’t a std::tuple<T>?

std::make_tuple<T> Produces std::tuple<T>
int    int
const int   
int&  
int&& 
std::reference_wrapper<      int  > int&
std::reference_wrapper<const int  >
std::reference_wrapper<      int& >
std::reference_wrapper<      int&&>

Answer: When T is subject to decay or decays to a reference_wrapper.

Decay is a term in the C++ standard that refers to the changes of type that typically occur when something is passed by value to a function:

  • References decay to the underlying type.
  • cv-qualifiers (const and volatile) are removed.
  • Arrays decay to pointers.
  • Function decay to function pointers.

But make_tuple adds an additional wrinkle: If the decayed type is a reference_wrapper, then the result is the underlying reference.

We don’t want any of these transformations to occur. If you select a type from a tuple that is a reference, then you want the resulting tuple to have the same reference type.

So we can’t use make_tuple. We’ll specify the desired tuple type explicitly.

template<typename Tuple, std::size_t... Ints>
auto select_tuple(Tuple&& tuple, std::index_sequence<Ints...>)
{
 return std::tuple<std::tuple_element_t<Ints, Tuple>...>(
    std::get<Ints>(std::forward<Tuple>(tuple))...);
}

or alternatively

template<typename Tuple, std::size_t... Ints>
std::tuple<std::tuple_element_t<Ints, Tuple>...>
select_tuple(Tuple&& tuple, std::index_sequence<Ints...>)
{
 return { std::get<Ints>(std::forward<Tuple>(tuple))... };
}

Okay, now that we have this helper function, we can do a bunch of fancy tuple manipulation.

Which we’ll do next time.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.

Feedback