10

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;
}
Olof
  • 5,348
  • 4
  • 25
  • 27
  • 1
    It appears that in the constructor you want read from the strings. Be aware that the order the constructors are called is unspecified for your implementation of `parse`. – Johannes Schaub - litb Dec 25 '12 at 23:59
  • @JohannesSchaub-litb, interesting point. That can be done, too: http://liveworkspace.org/code/MTk2Nj$0 provided that the component types are distinct (possible but too long to show as an example with duplicated types). – rici Dec 26 '12 at 01:51
  • 7 year later... Consider [std::apply](https://en.cppreference.com/w/cpp/utility/apply) since C++17. – kyb Mar 11 '20 at 17:41

3 Answers3

8

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.

Community
  • 1
  • 1
jogojapan
  • 68,383
  • 11
  • 101
  • 131
  • "Unfortunately, this kind of specialization isn't possible with function templates." is not exactly true. Well, it is true, but you can get much the same effect from overloading and template deduction. See here: http://liveworkspace.org/code/MjU4Nj$0 – rici Dec 26 '12 at 04:19
  • @rici I don't think you can solve the problem described in the question using overloading, because the existing version of `parse`, which would have to remain intact, is so general that it would cause ambiguities. But the mechanism used for structs/classes is that of partial template specialization, which enables the disambiguation as described above. – jogojapan Dec 26 '12 at 04:28
  • TBH, I have no idea what he wants to do. I read the question several times, and I still don't understand what interface he'd like to present. However, it would certainly be possible to use deduction by adding an unevaluated argument to parse, which is occasionally a useful technique. (eg. `auto a = parse(ifs, Into());`, which could be added without changing the existing interface.) (I agree that in this case, the struct is better, which is why I used it myself :) ) – rici Dec 26 '12 at 04:38
  • `Into()` would be evaluated (although it may compile to a no-op). (But yes, changing the interface of the function -- or, for that matter, changing the name of the function --, is always a solution to ambiguity problems.) – jogojapan Dec 26 '12 at 04:47
  • 1
    Your solution is quite similar to the answer from @rici. It was a tough choice to choose the accepted answer. – Olof Dec 28 '12 at 08:30
2

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

Community
  • 1
  • 1
rici
  • 234,347
  • 28
  • 237
  • 341
  • +1 for using a struct for the specialization. (The reason I posted my own answer is that I think the `parse` function from the OP's code should translate to a static function of the struct, rather than a call to the constructor of the struct.) – jogojapan Dec 26 '12 at 01:59
  • @jogojapan legitimate design decision. I don't see that it makes much difference, though. But tastes vary. You might want to think about litb's point about order of construction, unless you already looked at my version on lws. – rici Dec 26 '12 at 02:06
1

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>());
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380