【问题标题】:How do I write variadic templates, that can't accept zero arguments?如何编写不能接受零参数的可变参数模板?
【发布时间】:2013-10-27 00:57:49
【问题描述】:

这是一个打印参数的可变参数模板。

#include <string>
#include <iostream>

void Output() {
    std::cout<<std::endl;
}

template<typename First, typename ... Strings>
void Output(First arg, const Strings&... rest) {
    std::cout<<arg<<" ";
    Output(rest...);
}

int main() {
    Output("I","am","a","sentence");
    Output("Let's","try",1,"or",2,"digits");
    Output(); //<- I do not want this to compile, but it does.

    return 0;
}

有没有办法在没有“无参数”调用工作的情况下获得此功能,并且不必每次都编写两个函数?

【问题讨论】:

  • 你可以写一个单参数版本而不是零参数版本。您可以将两者都隐藏在命名空间中,并通过需要 > 0 个参数的函数调用它们。
  • 我回答了您的问题,但我不确定您所说的“不必每次都编写两个函数”是什么意思?

标签: function templates c++11 parameters variadic-templates


【解决方案1】:

您可能希望保持第一个参数和其余参数的分离,您可以使用:

template<typename First, typename ... Rest>
void Output(First&& first, Rest&&... rest) {
    std::cout << std::forward<First>(first);
    int sink[]{(std::cout<<" "<<std::forward<Rest>(rest),0)... };
    (void)sink; // silence "unused variable" warning
    std::cout << std::endl;
}

请注意,我使用完美转发来避免复制任何参数。以上具有避免递归的额外好处,因此可能会生成更好(更快)的代码。

我编写sink 的方式也保证了从rest 扩展的表达式是从左到右计算的——与仅编写辅助函数template&lt;typename...Args&gt;void sink(Args&amp;&amp;...){} 的幼稚方法相比,这很重要。

Live example

【讨论】:

    【解决方案2】:

    从转发类型函数调用该函数,并有一个这样的 static_assert:

    template <typename ... Args>                                                       
    void forwarder(Args ... args) {                                                    
        static_assert(sizeof...(args),"too small");                                    
        Output(args...);                                                               
    }  
    

    【讨论】:

      【解决方案3】:

      据我所知有两个问题:

      1. 如何避免不带参数的Output() 调用。
      2. 有没有更简单的方法来结束编译时递归?

      我对第1项的解决方案如下:

      template<typename T>
      void Output(const T & string) {
          std::cout<<string<<std::endl;
      }
      
      template<typename First, typename ... Strings>
      void Output(const First & arg, const Strings & ... rest) {
          std::cout<<arg<<" ";
          Output(rest...);
      }
      

      基本上,我不是在模板列表为空时结束递归,而是在它只包含一种类型时结束它。上面和问题中的代码有一个区别:如果在最后一项之后不输出任何空格。相反,它只是输出换行符。

      对于第二个问题,请参阅上面 Daniel Frey 的答案。我真的很喜欢这个解决方案,虽然花了一些时间来掌握它(我赞成这个答案)。同时我发现它使代码更难阅读/理解,因此更难维护。目前,除了小型个人代码 sn-ps 之外,我不会使用该解决方案。

      【讨论】:

      • +1 用于 OP 的另一个选项,但您应该(像我一样)使用完美转发以避免不必要的开销。另外,我同意我的解决方案不是最容易理解的,但避免递归通常非常重要,因为它可能对代码大小和性能产生严重负面影响,我不得不多次见证这一点野生。额外的复杂性也得到了很好的控制,并且没有反映在面向用户的界面中。这就是为什么我有一套技巧来避免递归。 FWIW,我的解决方案避免了多余的最后一个空格:)
      • @DanielFrey 总的来说,我同意完美转发,但对于这种特殊情况,我认为这是不必要的,因为函数是“const”。也不需要转发到operator&lt;&lt;(),因为它在几乎所有情况下都接受对输出值的 const 引用。关于递归,我希望优化编译器将内联最后一次调用;尽管 gcc 不会这样做,除非您至少指定 -O1。请参阅我的更新答案。
      • 这里也可以使用常量引用,只是不要像原来那样复制(以及 OP)。对于递归,这取决于operator&lt;&lt; 会做什么,因为您可以提供具有自己版本的 UDT,这些版本可能是内联/可见的,然后优化器有时会退出并开始生成非常糟糕的代码。
      猜你喜欢
      • 1970-01-01
      • 2019-11-22
      • 1970-01-01
      • 2016-12-11
      • 1970-01-01
      • 2011-04-15
      • 2016-12-01
      • 2015-05-06
      • 1970-01-01
      相关资源
      最近更新 更多