C++ Idioms to Handle Tupples

Consider the following code that will NOT compile

template< typename... Args>
void for_every(std::tuple<Args...> & t)
{
	const size_t n= sizeof...(Args);
	for(size_t i=0; i<n; ++i )
		SomeAction(std::get<i>(t));
}

This will not compile because for get<i>:act to be compiled, ‘i’ needs to be known at compile time, but ‘i’ changes at runtime. There are a few ways of addressing this issue.

A Siimple Solution


One way [1] of dealing with this which I find simplest is shown below:

  template<size_t N>
  struct identity { enum {value=N};};
  
  template< typename... Args>
  struct ArchiveTuple
  {
    ArchiveTuple(Archive& out_, std::tuple<Args...>& t_) :
      t(t_),
      ar(out_)
    {}

    template<size_t N>
    void act(identity<N>)
    {
      const size_t k = sizeof...(Args)-N;
      Serialize(ar, std::get<k>(t));
      act(identity<N-1>());
    }


    void act(identity<0>)
    {}
  private:
    std::tuple<Args...>& t;
    Archive& ar;
  };

  template<typename Archive, typename... Args>
  void serialize(Archive& ar, std::tuple<Args...>& t)
  {
    ArchiveTuple<Archive, Args...> arch(ar, t);
    arch.act(identity<sizeof...(Args)>());
  }

The act function is called recursively for decreasing values of N until N becomes zero when the non-templated function is called. This exploits the rule that overload resolution puts non-templated functions higher up in the pecking order.

Template Specialisation


Another idiom[2] would be

template<size_t N>
struct Action
	{
	template< typename... Args>
	static void act(std::tuple<Args...> &t)
		{
        const size_t k = sizeof...(Args)-N;
		std::cout << std::get<k>(t) << std::endl;
		Action<N - 1>::act(t);
		}
	};

template<>
struct Action<0>
	{
	template< typename... Args>
	static void act(std::tuple<Args...> & t)
		{
		}
	};

A test sample using the above code is listed below.

int main()
{
	auto mytuple = std::make_tuple(
                0,
                std::string("Hello"), 
                100.8, 
                true);
	Action<std::tuple_size<decltype(mytuple)>::value>::act(mytuple);
    return 0;
}

Here template specialisation ensures that Action::act is called when N is zero, thus terminating the recursion. The question then arrises as to whether functions can be specialised as shown below:

  template<typename Archive, typename... Args>
  struct ArchiveTuple
  {
    ArchiveTuple(Archive& out_, std::tuple<Args...>& t_) :
      t(t_),
      ar(out_)
    {}

    template<size_t N>
    void act()
    {
      const size_t k = sizeof...(Args)-N;
      std::cout << std::get<k>(t);
      act<N - 1>();
    }


    template<>
    void act<0>()
    {}
  private:
    std::tuple<Args...>& t;

  };

  template<typename... Args>
  void serialize(std::tuple<Args...>& t, const unsigned int /* version */)
  {
    ArchiveTuple<Archive, Args...> arch( t);
    arch.act<sizeof...(Args)>();
  }
}

Although this compiles with the Visual C++ compiler, it is non-standard and GCC 4.9 does not accept it. “Explicitly specialized members need their surrounding class templates to be explicitly specialized as well.” The difference between this and the first idiom is that here we are trying to use function template specialisation, whereas in the first one we used class template specialisation.

SFINAE


The most elegant solution which took me a while to appreciate is listed below.
MLPack’s neural network [3] components use this idiom:


  template<size_t I = 0, typename... Tp>
  typename std::enable_if<I < sizeof...(Tp), void>::type
  ResetParameter(std::tuple<Tp...>& network)
  {
    Reset(std::get<I>(network));
    ResetParameter<I + 1, Tp...>(network);
  }
  template<size_t I = 0, typename... Tp>
  typename std::enable_if<I == sizeof...(Tp), void>::type
  ResetParameter(std::tuple<Tp...>& /* unused */) 
  { /* Nothing to do here */ }

You would then call it using:

  ResetParameter(network);

where network is of type tuple.
Here there is no overload resolution. SFINAE (substituition failure is not an error) is used to create two distinct template functions and the empty function is called to terminate the recursion.

References

  1. A solution from stackoverflow whose source I am unable to retrace
  2. Emsr’s solution http://stackoverflow.com/questions/1198260/iterate-over-tuple
  3. http://www.mlpack.org as of Feb. 27, 2016
Advertisements

About The Sunday Programmer

Joe is an experienced C++/C# developer on Windows. Currently looking out for an opening in C/C++ on Windows or Linux.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s