Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Translating a std::tuple into a template parameter pack

I have a

typedef std::tuple<A, B> TupleType;

and would like to use the list of classes for a "template".

Suppose I have:

template<typename... args>
std::tuple<args...> parse(std::istream &stream) {
  return std::make_tuple(args(stream)...);
}

and that I can successfully use it with:

auto my_tuple = parse<A, B>(ifs);

is it possible to avoid having to specify the class list A,B if I already have a

typedef std::tuple<A,B> TupleType;

where the list A,B is already present?

an example:

#include <cstdlib>  // EXIT_SUCCESS, EXIT_FAILURE
#include <iostream> // std::cerr
#include <fstream>  // std::ifstream
#include <tuple>    // std::tuple

class A {
public:
  A(std::istream &);  // May throw FooBaarException 
};

class B {
public:
  B(std::istream &); // May throw FooBaarException 
};

template<typename... args>
std::tuple<args...> parse(std::istream &stream) {
  return std::make_tuple(args(stream)...);
}

int main() {
  std::ifstream ifs;
  ifs.exceptions(ifstream::eofbit | ifstream::failbit | ifstream::badbit);
  int res = EXIT_FAILURE;
  try {
    ifs.open("/some/file/path", std::ios::in | std::ios::binary);
    auto my_tuple = parse<A, B>(ifs); // my_tuple is of the type std::tuple<A,B>
    /* Here do something interesting with my_tuple */ 
    res = EXIT_SUCCESS;
  } catch (ifstream::failure e) {
    std::cerr << "error: opening or reading file failed\n";
  } catch (FooBaarException e) {
    std::cerr << "error: parsing in a constructor failed\n";
  }
  return res;
}
like image 293
Olof Avatar asked Dec 25 '12 20:12

Olof


3 Answers

The underlying problem in your situation seems to be that you'd like to specialize the function template parse for the special case when the template argument is a std::tuple. Unfortunately, this kind of specialization isn't possible with function templates.

However, it is possible with class templates.

So, as a first step, you could define parse as a static function of a struct, like this:

using std::istream;
using std::tuple;
using std::make_tuple;

struct A { A(const istream &) {} };
struct B { B(const istream &) {} };

template <typename... Args>
struct parser
{
  /* Your original function, now inside a struct.
     I'm using direct tuple construction and an
     initializer list to circumvent the order-of-
     construction problem mentioned in the comment
     to your question. */
  static tuple<Args...> parse(const istream &strm)
  { return tuple<Args...> {Args(strm)...}; }
};

template <typename... Args>
struct parser<tuple<Args...>>
{
  /* Specialized for tuple. */
  static tuple<Args...> parse(const istream &strm)
  { return parser<Args...>::parse(strm); }
};

You can then call it in the desired way:

int main()
{
  typedef tuple<A,B> tuple_type;
  auto tup = parser<tuple_type>::parse(std::cin);
  return 0;
}

As a second step, you can define a function template (again) which passes the arguments on to the right specialization of the struct:

template <typename... Args>
auto parse(const istream &strm) -> decltype(parser<Args...>::parse(strm))
{ return parser<Args...>::parse(strm); }

And now you can use it in exactly the way you wanted:

int main()
{
  typedef tuple<A,B> tuple_type;
  auto tup = parse<tuple_type>(std::cin);
  return 0;
}

(And you can still use it in the old way, too: auto tup = parse<A,B>(std::cin).)


Remark. As mentioned in the comment to parser::parse(), I used direct tuple construction instead of make_tuple to avoid problems with the order of construction of the tuple elements. This is not directly related to your question, but a good thing to do. See how to avoid undefined execution order for the constructors when using std::make_tuple.

like image 130
jogojapan Avatar answered Oct 15 '22 00:10

jogojapan


There is a standard idiom for this kind of thing. [1]

// Define the "shape" of the template
template<typename Tuple> struct TupleMap;
// Specialize it for std::tuple
template<typename...T> struct TupleMap<std::tuple<T...>> {
  using type = std::tuple<T...>;  // not necessary but saves typing
  // ... inside here, you have access to the parameter pac
}

Here's an example of using it, which might or might not fit your expectations (your example doesn't really indicate your expected use, since it lacks the typedef you promise in your question): liveworkspace.org.

Since litb raised the point, it is possible to force the tuple-components to be constructed in left-to-right order, illustrating another interesting idiom: comb inheritance. See lws.

(Since lws might disappear again, who knows, I'll paste the code here as well):

#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>

// Define the "shape" of the template
template<typename Tuple> struct TupleMap;
// Specialize it for std::tuple
template<typename...T> struct TupleMap<std::tuple<T...>> {
   using type = std::tuple<T...>;  // not necessary but saves typing

   type value;

   template<typename Arg>
   TupleMap(Arg&& arg)
       : value(T(std::forward<Arg>(arg))...) {
   }

   operator type() { return value; }
};

//Try it out:
using std::get;  // Note 2
using Numbers = std::tuple<char, double, int>;

// Note 3
std::ostream& operator<<(std::ostream& out, const Numbers& n) {
   return out << get<0>(n) << ' ' << get<1>(n) << ' ' << get<2>(n);
}

int main() {
    std::cout << TupleMap<Numbers>(93.14159);
    return 0;
}

[1] At least, I think it's a standard idiom. I use it a lot, and think of it as the "can-opener" pattern.

[2] This is needed (or at least, it's my style) to allow the use of get with tuple-like templates defined outside of std. Doing it this way allows ADL to find the appropriate definition of get without forcing me to add specializations to std::get. In this way, it's similar to the standard ADL idiom for begin and end.

[3] You can search SO for a cool hack to specialize operator<< for all tuples. There's a simpler one which can be used for specific tuples, but that's all off-topic for this question, so I just did something easy and dependency free. Note that this works because of the conversion operator in TupleMap

like image 37
rici Avatar answered Oct 15 '22 00:10

rici


The basic approach is to create a sequence of indices 0, ..., std::tuple_size<Tuple>::value - 1 as a parameter pack Indices and call your function with parse<typename std::tuple_element<Tuple, Indices>::type...>(stream). You'd probably encapsulate the logic into a function parse_tuple<Tuple>(stream) (and some function this one delegates to) which in the end delegates to parse<...>(stream).

First, here is a class template and a function to create a sequence of indices based on the size of a std::tuple. The indices are needed to obtain a list of type from std::tuple:

template <int... Indices> struct indices;
template <> 
struct indices<-1> {                // for an empty std::tuple<> there is no entry
    typedef indices<> type;
};
template <int... Indices>
struct indices<0, Indices...> {     // stop the recursion when 0 is reached
    typedef indices<0, Indices...> type;
};
template <int Index, int... Indices>
struct indices<Index, Indices...> { // recursively build a sequence of indices
    typedef typename indices<Index - 1, Index, Indices...>::type type;
};

template <typename T>
typename indices<std::tuple_size<T>::value - 1>::type const*
make_indices() {
    return 0;
}

With this in place, it is quite easy to extract the sequence of types from a std::tuple<T...>:

template<typename T, int... Indices>
T parse_tuple(std::istream &stream, indices<Indices...> const*) {
    return parse<typename std::tuple_element<Indices, T>::type...>(stream);
}
template <typename T>
T parse_tuple(std::istream& stream) {
    return parse_tuple<T>(stream, make_indices<T>());
}
like image 1
Dietmar Kühl Avatar answered Oct 15 '22 01:10

Dietmar Kühl