【问题标题】:int a[] { (functioncall(a1, a2), 0)...}; (void(a)); What does this syntax do/mean?int a[] { (functioncall(a1, a2), 0)...}; (无效(a));这个语法是做什么/意味着什么?
【发布时间】:2015-11-10 00:17:13
【问题描述】:

我看到这篇帖子variadic template function to concatenate std::vector containers 建议使用以下语法:

template<typename T>
void append_to_vector(std::vector<T>& v1, const std::vector<T>& v2) {
  std::cout << v2[0] << std::endl;
  for (auto& e : v2) v1.push_back(e);
}


template<typename T, typename... A>
std::vector<T> concat_version3(std::vector<T> v1, const A&... vr) {
    int unpack[] { (append_to_vector(v1, vr), 1)... };
    (void(unpack));
    return v1;
}

我开始玩弄它以了解它是如何工作的,因为我还没有看到这个:

int unpack[] { (append_to_vector(v1, vr), 0)... };
(void(unpack));

似乎这是某种动态生成的初始化列表,也有副作用?我也对上面的0 无关紧要感到困惑。我替换了 -1 和 5,这些值中的每一个也都可以正常工作。

那么有人可以告诉我这种技术/语法的名称以及上面两行中到底发生了什么吗?如果我错过了相关的 SO 帖子,我将非常感谢任何指点并道歉。

【问题讨论】:

  • 这种技术达到了为每个给定的可变参数调用一次函数append_to_vector(v1, X)的目的,X被每个元素依次替换。我叫它“虚拟数组逗号操作符hack”,不知道有没有正式名称:)

标签: c++ templates c++11 variadic-templates list-initialization


【解决方案1】:
int unpack[] { (append_to_vector(v1, vr), 1)... };
//        ^^ |                          |   ||| |    array of ints
//           ^                          |    |  ^    array initializer
//                                      ^    |       comma operator
//                                          ^^^      pack expansion

这将创建一个ints 数组,其中包含与参数包vr 的大小一样多的元素。数组中的每个元素都是1,这是comma operator 在评估两个参数后返回的内容。最后的省略号表示参数包vrpack expansion 正在完成。

因此,如果您将函数调用为concat_version3(v1, v2, v3),其中所有参数都是vectors,那么上述表达式将导致

int unpack[]{ (append_to_vector(v1, v2), 1), (append_to_vector(v1, v3), 1) };

braced-init-list 中计算表达式的好处是计算顺序是固定的并且从左到右发生。

§8.5.4/4 [dcl.init.list]

braced-init-listinitializer-list 中,initializer-clauses,包括任何由包扩展( 14.5.3),按照它们出现的顺序进行评估

所以你可以保证v2v3 之前被附加到v1,这就是你想要的。


(void(unpack));

这只是避免编译器发出未使用的变量警告的一种方法。


现在,我会以不同的方式编写您的 unpack 初始化。

int unpack[] { 1, (append_to_vector(v1, vr), 1)... };
//             ^^

在原始版本中,如果您将函数称为concat_version3(v1),即使用空参数包,则代码将无法编译,因为您将尝试创建一个零大小的数组,添加额外的元素可以解决该问题.

此外,如果您在不知道 append_to_vector 的返回类型是什么的更通用代码中使用上述表达式,那么您还需要防止它返回重载类型的可能性逗号运算符。在这种情况下,你会写

int unpack[] { 1, (append_to_vector(v1, vr), void(), 1)... };

通过在两者之间添加void() 表达式,您可以确保没有选择重载的逗号运算符,并且始终调用内置的逗号运算符。


最后,如果你有一个能理解fold expressions的编译器,你可以去掉整个数组的技巧,直接写

template<typename T, typename... A>
std::vector<T> concat_version3(std::vector<T> v1, const A&... vr)
{
    (void)(((append_to_vector(v1, vr), void()), ...));
    return v1;
}

Live demo

注意:void 转换后的额外括号是必需的,因为 clang bug

【讨论】:

  • 非常感谢这个非常彻底的回答。非常感谢。
  • 折叠表达式也可以使用void 强制转换(以防止逗号重载)。
  • @T.C.谢谢,没有考虑到这种可能性,不知何故折叠表达式似乎太酷了,不会受到这种陷阱的影响:) 现在已修复。
  • 在另一个线程上有人问我为什么不支持:append_to_vector(v1, vr)...; 具有相同的效果并丢弃返回值,我没有答案
  • @M.M 我的猜测是允许省略号应用于参数包以外的其他内容以进行扩展,而无需干预运算符,这会导致语法歧义。出于类似的原因,我一直认为折叠表达式需要括号括起来。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-19
  • 1970-01-01
  • 2016-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多