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).