嵌套 std::tuple 的 C++17 递归扁平化

C++17 recursive flattening of nested std::tuple

提问人:alexpanter 提问时间:11/10/2023 更新时间:11/10/2023 访问量:77

问:

我有一个函数,它应该从可变参数模板表达式构造一个。该函数如下所示(包括注释外的失败尝试):unpackstd::tuple

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!
}

该函数是一系列模板化的重载函数,我用它来检查是否可以从 .此函数将一些返回值放在 中,其中包含一个布尔值和一个错误消息的字符串。writeTypestd::stringstreamWriteTypeStruct

顶级域名;我正在为命令控制台编写一个基于字符串的解释器。

这里提供了该问题的完整演示:如何为基于文本的命令控制台存储参数化强类型函数,特别是@jarod42的答案。我正在使用这个函数而不是.unpackextract

关于如何展平 here,有一个很好的解释,但我希望这种代码可以通过首先不创建嵌套元组来避免。std::tuple

但我似乎无法弄清楚正确的语法。

C++ C++17 可变模板 stdtuple

评论

0赞 Yakk - Adam Nevraumont 11/10/2023
如果其中一个 or 是元组,您真的要展平它们吗?(想象一下,有人给你一个不透明的类型,恰好是一个元组......TTs...
0赞 alexpanter 11/10/2023
@Yakk-AdamNevraumont:是的,在我的特殊情况下,我总是想将它们展平/我没有理由假设它们可能是元组,因为它们取自字符串中(空格-)分隔的单词。

答:

2赞 Yakk - Adam Nevraumont 11/10/2023 #1

我会从反序列化开始:

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;
}

它的语义非常简单。

template<class...Ts>
std::tuple<Ts...> unpack(std::stringstream& ss) {
  return std::tuple<Ts...>{ unpack<Ts>(ss)... };
}

这些内部的评估顺序是有保证的。(注意:这是因为我使用了 ctor,而不是函数调用。程序员可以很容易地对上述内容进行“无所事事”的更改,并以一种取决于您使用的编译器的方式可怕地破坏它。{}{}

您的大多数尝试都存在一个问题,即它们处理不好(终止情况)。unpackunpack<>

调用而不是的那些(即,保证通过 1,并且在 0 处消除解包调用)最终会得到 1 层太多的元组,是你用元组包装的元组包。unpack<Ts>...unpack<Ts...>unpack<Ts>...

return std::tuple_cat(std::tuple<T>(t), std::make_from_tuple(unpack<Ts>(ss)...));

这很接近。要修复它,请执行以下操作:

return std::tuple_cat(std::tuple<T>(t), unpack<Ts>(ss)...);

只需去除 S 上多余的一层; 已返回一个元组。然后,我们 ,它支持任意数量的元组。tupleunpackunpacktuple_cat

我们可以尝试优化它:

return std::tuple_cat(std::tuple<T>(t), unpack<Ts...>(ss));

但这遇到了空洞的问题。unpack<>

我在这里遇到的一个问题是,你把所有东西都放到一个元组里,只是为了把它再次取出。

空的也可以处理。添加之前:unpack<>unpack

template<bool empty_pack=true>
std::tuple<> unpack(std::stringstream& ss) { return {}; }

然后应该调用该重载。unpack<>

但是,我发现所有这些都不如从元组版本调用的 1 元素版本优雅。

最后,我们可以编写一个元组扁平化函数,该函数接受任何一组参数,如果其中任何一个是元组,则将其扁平化,并返回融合列表。我个人认为这个功能很诱人,也是一个坏主意,因为它会立即“解决”一个问题,并在以后导致棘手的问题。

这有点像编写一个函数,该函数取消转义文本,但取消转义任意数量的层,直到不再有转义序列。或者一个逃避文本,但拒绝逃避任何已经逃脱的东西的人。

你把它扔进一个业务逻辑链中,它“解决”了一个不均匀转义或未转义的数据问题......并以无法修复的方式破坏事物。

评论

0赞 alexpanter 11/10/2023
很好的解释 - 谢谢!我确实解决了我遇到的最后一个问题。