【问题标题】:C++11 variable number of arguments, same specific typeC ++ 11可变数量的参数,相同的特定类型
【发布时间】:2013-08-03 18:17:57
【问题描述】:

问题很简单,我将如何实现一个带有可变数量参数的函数(类似于可变参数模板),但是所有参数都具有相同的类型,比如 int。

我正在考虑类似的事情;

void func(int... Arguments)

或者,对类型的递归静态断言不起作用?

【问题讨论】:

  • 如果您需要可变数量的 int 参数,为什么不将向量传递给它呢?这些论点会做什么?
  • 哦>.
  • 不要使用“...”。省略号是 C 的危险遗留物。请改用更高级别的 C++ 构造和库。 (“C++ 编码标准”,Sutter/Alexandrescu)
  • @DanielDaranas,OP 指的是参数包,而不是 C 风格的可变参数省略号。
  • @Shark: 1. 以免使用堆栈。 2. 以免依赖<vector> 3. 更加灵活。 4. 不那么灵活,即不要让某人在向量中传递无数个元素。 5. 成为 constexpr.

标签: c++ c++11 variadic


【解决方案1】:

一种可能的解决方案是使参数类型成为可以由大括号初始化器列表初始化的容器,例如std::initializer_list<int>std::vector<int>For example:

#include <iostream>
#include <initializer_list>

void func(std::initializer_list<int> a_args)
{
    for (auto i: a_args) std::cout << i << '\n';
}

int main()
{
    func({4, 7});
    func({4, 7, 12, 14});
}

【讨论】:

  • 说数字不是常数,这种方法是否仍然有效?
  • 这很好,但这里的参数数量只有在运行时才知道。 initializer_list 缺少 constexpr 版本。我需要那个:(
  • 只要函数不采用可变数量的引用,它就可以工作。 std::initializer_list&lt;int&amp;&gt; 不起作用
  • std::initialiser_list 存在设计缺陷,因为它不能感知移动。因此,在传递大型或不可复制类型时,您应该更喜欢 Jonathan Wakely 的解决方案(如下),它允许完美转发。
  • 这通过值传递那些值{4,7} 对吗?即使它们被命名为 int 变量?
【解决方案2】:

这是一个从重载集中删除函数的版本,而不是给出静态断言。这允许您提供在类型不完全相同时可以使用的函数的其他重载,而不是无法避免的致命 static_assert。

#include <type_traits>

template<typename... T>
  struct all_same : std::false_type { };

template<>
  struct all_same<> : std::true_type { };

template<typename T>
  struct all_same<T> : std::true_type { };

template<typename T, typename... Ts>
  struct all_same<T, T, Ts...> : all_same<T, Ts...> { };

template<typename... T>
typename std::enable_if<all_same<T...>::value, void>::type
func(T...)
{ }

如果您想支持完美转发,您可能希望在检查它们之前衰减类型,以便函数将接受左值和右值参数的混合,只要它们具有相同的类型:

template<typename... T>
typename std::enable_if<all_same<typename std::decay<T>::type...>::value, void>::type
func(T&&...)
{ }

或者,如果您有测试逻辑连接的通用特征,您可以使用std::is_same 来完成,而不是编写自己的all_same

template<typename T, typename... Ts>
typename std::enable_if<and_<is_same<T, Ts>...>::value, void>::type
func(T&&, Ts&&...)
{ }

因为这需要至少一个参数,所以您还需要另一个重载来支持零参数情况:

void func() { }

and_ 助手可以这样定义:

template<typename...>
  struct and_;

template<>
  struct and_<>
  : public std::true_type
  { };

template<typename B1>
  struct and_<B1>
  : public B1
  { };

template<typename B1, typename B2>
  struct and_<B1, B2>
  : public std::conditional<B1::value, B2, B1>::type
  { };

template<typename B1, typename B2, typename B3, typename... Bn>
  struct and_<B1, B2, B3, Bn...>
  : public std::conditional<B1::value, and_<B2, B3, Bn...>, B1>::type
  { };

【讨论】:

  • 不幸的是,这与假定的 func(Foo... foos) 不是 100% 相同,因为如果 Foo 有一个非显式构造函数,func({x, y, z}) 将不起作用——编译器无法推断{x, y, z} 的类型,除非该类型在 func 的签名中指定。
【解决方案3】:

我认为你可以通过在从参数包中咀嚼你的参数时指定一个具体类型来做到这一点。比如:

class MyClass{};
class MyOtherClass{};

void func()
{
    // do something
}

template< typename... Arguments >
void func( MyClass arg, Arguments ... args )
{
    // do something with arg
    func( args... );
    // do something more with arg
}


void main()
{
    MyClass a, b, c;
    MyOtherClass d;
    int i;
    float f;

    func( a, b, c );    // compiles fine
    func( i, f, d );    // cannot convert
}

在一般情况下,void func( MyClass arg, Arguments ... args ) 将变为具有模板类型 T 的 void func( arg, Arguments ... args )

【讨论】:

    【解决方案4】:

    @Skeen 这个怎么样?

    template <typename T>
    void func_1(std::initializer_list<T>&& a) {
        // do something
    } 
    
    template <typename... T>
    void func(T&&... a) {
        func_1({std::forward<T>(a)...});
    } 
    
    int main() {
        func(1, 2, 3);
        // func(1, 2, 3, 4.0); // OK doesn't compile
    }
    

    【讨论】:

    【解决方案5】:

    如果您不想使用基于大括号的initializer_list/vector 并希望以参数包的形式将参数分开,那么下面的解决方案在编译时使用递归static_asserts 对其进行检查:

    #include<type_traits>
    
    template<typename T1, typename T2, typename... Error>
    struct is_same : std::false_type {};
    
    template<typename T, typename... Checking>
    struct is_same<T, T, Checking...> : is_same<T, Checking...> {}; 
    
    template<typename T>
    struct is_same<T,T> : std::true_type {};
    
    template<typename... LeftMost>
    void func (LeftMost&&... args)
    {
      static_assert(is_same<typename std::decay<LeftMost>::type...>::value, 
                    "All types are not same as 'LeftMost'");
      // ...
    }
    
    int main ()
    {
      int var = 2;
      func(1,var,3,4,5);  // ok
      func(1,2,3,4.0,5); // error due to `static_assert` failure
    }
    

    实际上,这个解决方案会检查所有关于第一个参数的参数。假设它是double,那么所有内容都将根据double 进行检查。

    【讨论】:

    • 使用转发引用 (LeftMost&amp;&amp;) 意味着如果您使用混合左值和右值调用函数,静态断言将失败,例如int i=0; func(i, 1); 所以最好检查 is_same&lt;typename std::decay&lt;LeftMost&gt;::type...&gt;
    • @JonathanWakely,同意。谢谢!
    【解决方案6】:

    因为我认为我没有看到这个解决方案,所以您可以为每种类型编写一个特定的函数(在您的情况下,只是 int),然后是一个采用可变参数类型的转发函数。

    写出每个具体的案例:

    然后针对每个特定情况:

    // only int in your case
    void func(int i){
        std::cout << "int i = " << i << std::endl;
    }
    

    那么你的转发功能是这样的:

    template<typename Arg0, typename Arg1 typename ... Args>
    void func(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){
        func(std::forward<Arg0>(arg0));
        func(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
    }
    

    这很好,因为当您还想接受另一种类型时,它是可扩展的。

    这样使用:

    int main(){
        func(1, 2, 3, 4); // works fine
        func(1.0f, 2.0f, 3.0f, 4.0f); // compile error, no func(float)
    }
    

    【讨论】:

      猜你喜欢
      • 2015-04-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-16
      • 2012-04-20
      • 2011-12-14
      • 2012-07-10
      相关资源
      最近更新 更多