Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does std::get work?

After trying to make a std::get<N>(std::tuple) method myself, I'm not so sure how it's implemented by compilers. I know std::tuple has a constructor like this,

tuple(Args&&... args);

But what exactly is args... assigned to? I think this is useful in order to know how std::get works because the arguments need to be placed somewhere in order to access them...

like image 855
Me myself and I Avatar asked Jun 18 '13 20:06

Me myself and I


Video Answer


1 Answers

Here is a crude toy implementation of a tuple-like class.

First, some metaprogramming boilerplate, to represent a sequence of integers:

template<int...> struct seq {};
template<int max, int... s> struct make_seq:make_seq< max-1, max-1, s... > {};
template<int... s> struct make_seq<0, s...> {
  typedef seq<s...> type;
};
template<int max> using MakeSeq = typename make_seq<max>::type;

Next, the tagged class that actually stores the data:

template<int x, typename Arg>
struct foo_storage {
  Arg data;
};

This tagging technique is a common pattern whenever we want to associate data with some tag at compile time (in this case, an integer). The tag (an int here) isn't used anywhere in the storage usually, it is just used to tag the storage.

foo_helper unpacks a sequence and a set of arguments into a bunch of foo_storage, and inherits from them in a linear fashion. This is a pretty common pattern -- if you are doing this a lot, you end up creating metaprogramming tools that do this for you:

template<typename Seq, typename... Args>
struct foo_helper {};
template<int s0, int... s, typename A0, typename... Args>
struct foo_helper<seq<s0, s...>, A0, Args...>:
  foo_storage<s0, A0>,
  foo_helper<seq<s...>, Args...>
{};

My crude tuple type, foo, creates a package of a sequence of indexes and the args, and passes it to the helper above. The helper then creates a bunch of tagged data holding parent classes:

template<typename... Args>
struct foo: foo_helper< MakeSeq<sizeof...(Args)>, Args... > {};

I removed everything from the body of foo, because it isn't needed to implement get.

get is pretty simple: we take the storage type (not the tuple type), and the explicit template argument N disambiguates which of the foo_storage<n, T> we are going to access. Now that we have the storage type, we simply return the data field:

template<int N, typename T>
T& get( foo_storage<N, T>& f )
 { return f.data; }
template<int N, typename T>
T const& get( foo_storage<N, T> const& f )
 { return f.data; }

We are using the overloading mechanisms of the C++ langauge to do the heavy lifting. When you call a function with a class instance, that instance as each of the parent classes are gone over to see if any of them can be made to match. With the N fixed, there is only one parent class that is a valid argument, so parent class (and hence T) is deduced automatically.

And finally, some basic test code:

#include <iostream>

int main() {
  foo<int, double> f;
  get<0>( f ) = 7;
  get<1>( f ) = 3.14;
  std::cout << get<0>(f) << "," << get<1>(f) << "\n";
}
like image 166
Yakk - Adam Nevraumont Avatar answered Oct 13 '22 19:10

Yakk - Adam Nevraumont