I have a function unpack
, which should construct an std::tuple
from a variadic template expression. The function looks like this (including out-commented failed attemps):
template<typename T, typename... Ts>
std::tuple<T,Ts...> unpack(std::stringstream& ss)
{
T t{};
WriteTypeStruct ret{};
if (writeType(ret, ss, t); (!ss || ret.err)) {
throw std::runtime_error(ret.msg);
}
//std::tuple args = {extract<std::decay_t<Ts>>(ss)...};
//std::tuple args = unpack<typename std::decay_t<Ts>...>(ss);
//return {t, unpack<Ts>(ss)...};
//return std::make_tuple(t, unpack<Ts>(ss)...);
//std::tuple<Ts...> unpacked = {unpack<Ts>(ss)...};
//return std::make_tuple(t, unpacked);
//return std::tuple_cat(std::tuple<T>(t), std::make_from_tuple(unpack<Ts>(ss)...));
return std::make_tuple(t, std::make_from_tuple<Ts...>(unpack<Ts>(ss)...)); // <-- cannot compile!
}
The function writeType
is a series of templated overloaded functions, and I use it to check if I can extract expected types from an std::stringstream
. This function places some return value inside the WriteTypeStruct
, which contains a boolean and a string for the error message.
TLDR; I'm writing a string-based interpreter for a command console.
A full presentation of the problem is presented here: How to store parametric, strongly typed function for a text-based command console , and in particular the answer by @jarod42. I'm using this unpack
function instead of extract
.
There is a great explanation on how to flatten an std::tuple
here, but I was hoping this kind of code may be avoided by not creating a nested tuple in the first place.
But I can't seem to figure out the correct syntax.
I'd start with deserialize:
template<class T>
T deserialize(std::stringstream& ss) {
T t{};
WriteTypeStruct ret{};
if (writeType(ret, ss, t); (!ss || ret.err)) {
throw std::runtime_error(ret.msg);
}
return t;
}
the semantics of this is pretty simple.
template<class...Ts>
std::tuple<Ts...> unpack(std::stringstream& ss) {
return std::tuple<Ts...>{ unpack<Ts>(ss)... };
}
order of evaluation within those {}
is guaranteed. (NOTE: this is because I used {}
ctor, and not a function call. A programmer can easily make a change to the above that "does nothing" and breaks it horribly in a way that depends on which compiler you use.)
A problem most of your unpack
attempts have is that they don't handle unpack<>
well (the terminating case).
The ones that call unpack<Ts>...
as opposed to unpack<Ts...>
(ie, pass 1 guaranteed, and the unpack call is eliminated at 0) end up with 1 too many layers of tuple, unpack<Ts>...
is a pack of tuples which you wrap up in tuples.
return std::tuple_cat(std::tuple<T>(t), std::make_from_tuple(unpack<Ts>(ss)...));
this is close. To fix it:
return std::tuple_cat(std::tuple<T>(t), unpack<Ts>(ss)...);
just remove that extra layer of tuple
on the unpack
s; unpack
already returns a tuple. We then tuple_cat
, which supports any number of tuples.
We could try to optimize this:
return std::tuple_cat(std::tuple<T>(t), unpack<Ts...>(ss));
but that runs into the empty unpack<>
problem.
A problem I have here is that you are putting everything into a tuple just to take it back out again.
The empty unpack<>
can also be handled. Before your unpack
add:
template<bool empty_pack=true>
std::tuple<> unpack(std::stringstream& ss) { return {}; }
then unpack<>
should call that overload.
However, I find all of these less elegant than a 1 element version that is called from the tuple version.
Finally, we could write a tuple flatten function that takes any set of arguments, and if any of them are tuples it flattens them, and returns the fused list. I personally find that function to be seductive and a bad idea, as it "solves" a problem immediately and leads to intractable problems later on.
It is a bit like writing a function that unescapes text, but unescapes any number of layers until there are no more escape sequences. Or one that escapes text, but refuses to escape anything already escaped.
You throw that into a business logic chain and it "solves" a problem of unevenly escaped or unescaped data... and breaks things in ways that cannot be fixed.