【问题标题】:Variadic template argument forwarding uses comma operator可变模板参数转发使用逗号运算符
【发布时间】:2020-04-22 17:13:00
【问题描述】:

最近我在学习 C++ 和可变参数模板。我尝试编写一个模板函数,它接受一个容器(我对其进行了简化,并且在这个问题中我只使用list)和一些其他参数,并将其他参数放置到容器中。我的代码如下所示:

#include <iostream>
#include <list>
#include <utility>

template <typename Container, typename... Args>
void my_emplace(Container &c, Args &&... args){
    c.emplace_back(std::forward<Args>(args)...);
}

int main(void) {
    std::list<int> l = {1, 2};
    my_emplace(l, 3, 4, 5);

    for (auto i : l)
        std::cout << i << ", ";
    return 0;
}

但是,代码不起作用:

$ g++ -std=c++17 test.cpp
...
/usr/include/c++/7/ext/new_allocator.h:136:4: error: new initializer expression list treated as compound expression [-fpermissive]
  { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我在互联网上找到了解决方案 -- c.emplace_back(std::forward&lt;Args&gt;(args)...);(c.emplace_back(std::forward&lt;Args&gt;(args)), ...); 替换。该代码与它完美配合。

这是我不明白的部分。我从未见过这种语法。

  • 为什么我的代码不起作用? This answer here 基本上说是正确的。
  • 我找到的正确解决方案实际上有什么作用?我从未使用过逗号运算符,但我发现在使用时,两个表达式都被计算并且第一个表达式的结果被丢弃。它在这里做什么?为什么没有它代码就不能工作?

感谢您的回答。

【问题讨论】:

  • 你用 3 个参数调用 emplace_back,但 'int constructor' 只需要 1 个
  • 好问题。附注:使用vector 作为您的默认容器(除非您有充分的理由选择list),并且不要使用字母l 作为标识符,因为很难看出区别1.
  • *通常不要使用单字母标识符,除非它们已经很好地建立起来,例如轴的“x、y、z”。
  • @xtofl @Yamahari :代码本身看起来不像这样,我缩短了它以简化我的问题。在函数体中,我还尝试使用多个容器,如vector(emplace_back)、set(emplace)等,并使my_emplace“通用”。
  • 太棒了!最好也朝着这些良好实践的方向进行简化:)。它可以减少挑剔的读者的注意力,并为您提供更好的答案。

标签: c++ templates variadic-templates perfect-forwarding comma-operator


【解决方案1】:

假设 args 是 (1, 2, 3, 4),这个调用:

c.emplace_back(std::forward<Args>(args)...);

基本意思是:

c.emplace_back(1, 2, 3, 4); 所以你不能用 emplace_back 的这些参数构造 int ,因此会出现编译错误。

还有这个电话:

(c.emplace_back(std::forward<Args>(args)), ...);

意思

c.emplace_back(1), c.emplace_back(2), c.emplace_back(3), c.emplace_back(4);

第二次调用用逗号运算符扩展整个表达式,它被称为Fold Expressions,它是在 C++17 中添加的,你可以阅读更多关于它的信息Fold Expression

【讨论】:

    【解决方案2】:

    emplace_back 会将其参数转发给 value_type 的构造函数,在本例中为 int。这意味着您可以拨打c.emplace_back(3),但不能拨打c.emplace_back(3,4,5)

    当传递了多个值时,你想多次调用emplace_back

    c.emplace_back(3), c.emplace_back(4), c.emplace_back(5);
    

    我们可以用上面的逗号操作符在一行中完成。

    您展开参数包的两种不同方式的工作方式相同。

    c.emplace_back(std::forward<Args>(args)...);
    // normal un-packing, translates to
    c.emplace_back(3, 4, 5);
    
    (c.emplace_back(std::forward<Args>(args)), ...);
    // un-packing with a fold-expression using the comma operator
    (c.emplace_back(3), c.emplace_back(4), c.emplace_back(5));
    

    【讨论】:

      【解决方案3】:

      从 C++17 开始,您可以使用折叠表达式(参见 super 和 Gaurav Dhiman 的答案)。

      在 C++17(C++11 和 C++14)之前,您可以获得类似的扩展表达式来初始化未使用的数组

      我是说

      template <typename Container, typename... Args>
      void my_emplace (Container &c, Args && ... args)
       {
         using unused = int[];
      
         (void)unused { 0, ((void)c.emplace_back(std::forward<Args>(args)), 0)... };
       }
      

      注意使用逗号运算符丢弃扩展表达式。

      还要注意c.emplace_back() 前面的(void)。在这种情况下(cstd::list)是多余的,但添加它是为了避免返回对象重新定义逗号运算符的潜在问题。

      【讨论】:

      • 为什么我们需要前导零?
      • @Kobi - 否则当args... 列表为空时会出现错误。
      猜你喜欢
      • 2011-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-21
      相关资源
      最近更新 更多