3

I have seen people write (on stack overflow itself, asking some even advanced concepts) something along the lines of:

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

and use it as

auto tup = parse<int, float, char>(stream);

How does the above code construct the tuple by parsing the stream? Is there any specific requirement on how data is to be put into the stream?

Casey
  • 41,449
  • 7
  • 95
  • 125
Arun
  • 3,138
  • 4
  • 30
  • 41

2 Answers2

2

For this to work there must be an implicit conversion from std::istream to all the types specified as template arguments.

struct A {
    A(std::istream&) {} // A can be constructed from 'std::istream'.
};

struct B {
    B(std::istream&) {} // B can be constructed from 'std::istream'.
};

int main() {
    std::istringstream stream{"t1 t2"};
    auto tup = parse<A, B>(stream);
}

It works by expanding the variadic list of types and constructs each type with the supplied std::istream as argument. It is then left to the constructor of each type to read from the stream.

Also be aware that the evaluation order of the constructors is not specified so you can't expect that the first type in the variadic list will read from the stream first etc.

The code as it is does not work with built in types as int, float and char as there is no conversion from std::istream to any of those types.

Felix Glas
  • 15,065
  • 7
  • 53
  • 82
  • Iam on VS2013 and I get the following error error C2144: syntax error : 'A' should be preceded by ')' – Arun Jan 21 '15 at 20:15
1

It works poorly. It relies on the target type having a constructor that takes an std::istream.

As many types don't have this, and you cannot add it to something like int, this is a bad plan.

Instead do this:

template<class T>
auto read_from_stream( std::istream& stream, T* unused_type_tag )
-> typename std::decay<decltype( T{stream} )>::type
{
  return {stream};
}
template<class T>
auto read_from_stream( std::istream& stream, T* unused_type_tag, ... )
-> typename std::decay<decltype( T(stream) )>::type
{
  return T(stream);
}

template<typename... args>
std::tuple<args...> parse(std::istream& stream)  {
  return std::tuple<args...>{
    read_from_stream(stream, (args*)nullptr)...
  };
}

now instead of directly constructing the arguments, we call read_from_stream.

read_from_stream has two overloads above. The first tries to directly and implicitly construct our object from an istream. The second explicitly constructs our object from an istream, then uses RVO to return it. The ... ensures that the 2nd one is only used if the 1st one fails.

In any case, this opens up a point of customization. In the namespace of a type X we can write a read_from_stream( std::istream&, X* ) function, and it will automatically be called instead of the default implementation above. We can also write read_from_stream( std::istream&, int* ) (etc) which can know how to parse integers from an istream.

This kind of point of customization can also be done using a traits class, but doing it with overloads has a number of advantages: you can inject the customizations adjacent to the type, instead of having to open a completely different namespace. The custom action is also shorter (no class wrapping noise).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • There is a big problem with that implementation, there is no guarantee that the parameters to `make_tuple` are evaluate left to right. This is an unspecified behavior. And in fact, clang and gcc behave the opposite. GCC http://coliru.stacked-crooked.com/a/0688e941dfc9c563 and CLANG http://coliru.stacked-crooked.com/a/d136ebd8dd32c43d – galop1n Jan 21 '15 at 21:37
  • Instead of using `std::make_tuple( blabla... )` you have to use `std::tuple { blabla... }` to enforce the evaluation order ( note the use of {} instead of () ). – galop1n Jan 21 '15 at 21:44
  • @galop1n *nod*, fixed. As an aside, find the inability to "directly construct via function" the parts of a tuple annoying: the above requires move-ability of the data, for no good reason. – Yakk - Adam Nevraumont Jan 21 '15 at 21:52
  • @Yakk, I am using vs2013 to use your code and it cries : error C2668: 'read_from_stream' : ambiguous call to overloaded function. Any ideas? – Arun Jan 22 '15 at 10:36
  • @arun eliminate first overload, and the `...` in second. It is only useful in a corner case anyhow. – Yakk - Adam Nevraumont Jan 22 '15 at 12:27