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:
the semantics of this is pretty simple.
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
unpackattempts have is that they don't handleunpack<>well (the terminating case).The ones that call
unpack<Ts>...as opposed tounpack<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.this is close. To fix it:
just remove that extra layer of
tupleon theunpacks;unpackalready returns a tuple. We thentuple_cat, which supports any number of tuples.We could try to optimize this:
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 yourunpackadd: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.