【问题标题】:Variable number of arguments in C++?C ++中可变数量的参数?
【发布时间】:2010-12-12 02:06:51
【问题描述】:

如何编写一个接受可变数量参数的函数?这可能吗,怎么做?

【问题讨论】:

  • 此时使用 C++11 这个问题的答案会有很大的不同
  • @K-ballo 我也添加了 C++11 示例,因为最近的一个问题最近问了同样的事情,我觉得这需要一个来证明关闭它是合理的stackoverflow.com/questions/16337459/…
  • 在我的答案中也添加了 pre C++11 选项,因此它现在应该涵盖大多数可用的选择。
  • @K-ballo 如果你需要强制参数类型,那么在 C++ 中没有办法做到这一点。没有像 foo(int ... values) 这样的构造:/ 如果你不在乎关于类型,那么是的,C++11 中的可变参数模板效果很好

标签: c++ variadic-functions


【解决方案1】:

C++11 中,您有两个新选项,正如 Alternatives 部分中的 Variadic arguments 参考页面 所述:

  • 可变参数模板也可用于创建采用可变数量的函数 论据。它们通常是更好的选择,因为它们不会对 参数的类型,不执行整数和浮点提升,以及 是类型安全的。 (C++11 起)
  • 如果所有变量参数共享一个公共类型,则 std::initializer_list 提供一个 访问变量参数的便捷机制(尽管语法不同)。

以下是显示两种选择的示例 (see it live):

#include <iostream>
#include <string>
#include <initializer_list>

template <typename T>
void func(T t) 
{
    std::cout << t << std::endl ;
}

template<typename T, typename... Args>
void func(T t, Args... args) // recursive variadic function
{
    std::cout << t <<std::endl ;

    func(args...) ;
}

template <class T>
void func2( std::initializer_list<T> list )
{
    for( auto elem : list )
    {
        std::cout << elem << std::endl ;
    }
}

int main()
{
    std::string
        str1( "Hello" ),
        str2( "world" );

    func(1,2.5,'a',str1);

    func2( {10, 20, 30, 40 }) ;
    func2( {str1, str2 } ) ;
} 

如果您使用gccclang,我们可以使用PRETTY_FUNCTION magic variable 来显示函数的类型签名,这有助于理解正在发生的事情。例如使用:

std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;

对于示例中的可变参数函数(see it live)将导致 int 如下:

void func(T, Args...) [T = int, Args = <double, char, std::basic_string<char>>]: 1
void func(T, Args...) [T = double, Args = <char, std::basic_string<char>>]: 2.5
void func(T, Args...) [T = char, Args = <std::basic_string<char>>]: a
void func(T) [T = std::basic_string<char>]: Hello

在 Visual Studio 中,您可以使用 FUNCSIG

更新 Pre C++11

Pre C++11 std::initializer_list 的替代品将是 std::vector 或其他 standard containers 之一:

#include <iostream>
#include <string>
#include <vector>

template <class T>
void func1( std::vector<T> vec )
{
    for( typename std::vector<T>::iterator iter = vec.begin();  iter != vec.end(); ++iter )
    {
        std::cout << *iter << std::endl ;
    }
}

int main()
{
    int arr1[] = {10, 20, 30, 40} ;
    std::string arr2[] = { "hello", "world" } ; 
    std::vector<int> v1( arr1, arr1+4 ) ;
    std::vector<std::string> v2( arr2, arr2+2 ) ;

    func1( v1 ) ;
    func1( v2 ) ;
}

可变参数模板的替代方案是variadic functions,尽管它们不是类型安全的,通常是error prone and can be unsafe to use,但唯一的其他潜在替代方案是使用默认参数,尽管它的用途有限。下面的示例是链接参考中示例代码的修改版本:

#include <iostream>
#include <string>
#include <cstdarg>
 
void simple_printf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
 
    while (*fmt != '\0') {
        if (*fmt == 'd') {
            int i = va_arg(args, int);
            std::cout << i << '\n';
        } else if (*fmt == 's') {
            char * s = va_arg(args, char*);
            std::cout << s << '\n';
        }
        ++fmt;
    }
 
    va_end(args);
}
 

int main()
{
    std::string
        str1( "Hello" ),
        str2( "world" );

    simple_printf("dddd", 10, 20, 30, 40 );
    simple_printf("ss", str1.c_str(), str2.c_str() ); 

    return 0 ;
} 

使用 可变参数函数 还附带对您可以传递的参数的限制,这在 draft C++ standard 部分 5.2.2 中进行了详细说明 函数调用 段落 7

当给定参数没有参数时,参数的传递方式使得接收函数可以通过调用 va_arg (18.7) 来获取参数的值。左值到右值 (4.1)、数组到指针 (4.2) 和函数到指针 (4.3) 标准转换是在参数表达式上执行的。在这些转换之后,如果参数没有算术、枚举、指针、指向成员的指针或类类型,则程序是非良构的。如果参数具有非 POD 类类型(第 9 条),则行为未定义。 [...]

【讨论】:

  • 您的typenameclass 的使用是故意的吗?如果有,请解释。
  • @kevinarpe 不是故意的,但它不会改变任何东西。
  • 是否可以使函数采用initializer_list 递归?
【解决方案2】:

您可能不应该这样做,而且您可能可以以更安全、更简单的方式做您想做的事情。从技术上讲,要在 C 中使用可变数量的参数,您需要包含 stdarg.h。从中您将获得va_list 类型以及对其进行操作的三个函数va_start()va_arg()va_end()

#include<stdarg.h>

int maxof(int n_args, ...)
{
    va_list ap;
    va_start(ap, n_args);
    int max = va_arg(ap, int);
    for(int i = 2; i <= n_args; i++) {
        int a = va_arg(ap, int);
        if(a > max) max = a;
    }
    va_end(ap);
    return max;
}

如果你问我,这是一团糟。它看起来很糟糕,不安全,并且充满了与您在概念上试图实现的目标无关的技术细节。相反,请考虑使用重载或继承/多态性、构建器模式(如 operator&lt;&lt;() 在流中)或默认参数等。这些都更安全:编译器会更多地了解您正在尝试做的事情,因此有更多的场合它可以在你炸断你的腿之前阻止你。

【讨论】:

  • 推测,您不能将引用传递给可变参数函数,因为编译器不知道何时通过值传递以及何时通过引用传递,并且因为底层 C 宏不一定知道如何处理引用-- 由于诸如提升规则之类的原因,您可以将什么传递给具有可变参数的 C 函数已经受到限制。
  • 是否需要在... 语法之前提供至少一个参数?
  • @Lazer 这不是语言或库的要求,但标准库没有给您提供告诉列表长度的方法。您需要呼叫者向您提供此信息,或者以某种方式自己弄清楚。例如,在printf() 的情况下,该函数会解析字符串参数以获得特殊标记,以确定它应该在变量参数列表中预期多少额外的参数。
  • 你应该在 C++ 中使用 &lt;cstdarg&gt; 而不是 &lt;stdarg.h&gt;
  • 可变数量的参数非常适合调试或填充某些数组的函数/方法。它也适用于许多数学运算,例如最大值、最小值、求和、平均值……当你不乱用它时,它就不会乱了。
【解决方案3】:

C++17 解决方案:完全类型安全 + 良好的调用语法

由于在 C++11 中引入了可变参数模板并在 C++17 中引入了折叠表达式,因此可以定义一个模板函数,在调用方站点,它可以像可变参数函数一样被调用,但具有优点:

  • 是强类型安全的;
  • 在没有参数数量的运行时信息或不使用“停止”参数的情况下工作。

这是混合参数类型的示例

template<class... Args>
void print(Args... args)
{
    (std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");

另一个强制类型匹配所有参数:

#include <type_traits> // enable_if, conjuction

template<class Head, class... Tail>
using are_same = std::conjunction<std::is_same<Head, Tail>...>;

template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
void print_same_type(Head head, Tail... tail)
{
    std::cout << head;
    (std::cout << ... << tail) << "\n";
}
print_same_type("2: ", "Hello, ", "World!");   // OK
print_same_type(3, ": ", "Hello, ", "World!"); // no matching function for call to 'print_same_type(int, const char [3], const char [8], const char [7])'
                                               // print_same_type(3, ": ", "Hello, ", "World!");
                                                                                              ^

更多信息:

  1. 可变参数模板,也称为参数包Parameter pack(since C++11) - cppreference.com
  2. 折叠表达式fold expression(since C++17) - cppreference.com
  3. 在 coliru 上查看 full program demonstration

【讨论】:

  • 有一天我会读到template&lt;class Head, class... Tail, class = std::enable_if_t&lt;are_same&lt;Head, Tail...&gt;::value, void&gt;&gt;
  • @Eladian 将其读作“仅当 HeadTail... 相同时才启用此东西”,其中“相同 >" 表示std::conjunction&lt;std::is_same&lt;Head, Tail&gt;...&gt;。将最后一个定义读作“Head 与所有Tail... 相同”。
  • 我们可以遍历args吗?
【解决方案4】:

在 c++11 中你可以这样做:

void foo(const std::list<std::string> & myArguments) {
   //do whatever you want, with all the convenience of lists
}

foo({"arg1","arg2"});

列表初始化 FTW!

【讨论】:

  • 参数可以是通用类型(Int、Char、Float 等),这对它们不起作用。
【解决方案5】:

在 C++11 中,有一种方法可以创建可变参数模板,这导致了一种非常优雅且类型安全的方法来拥有可变参数函数。 Bjarne 自己在C++11FAQ 中给出了printf using variable argument templates 的一个很好的例子。

就我个人而言,我认为这非常优雅,以至于在编译器支持 C++11 可变参数模板之前,我什至不会在 C++ 中使用可变参数函数。

【讨论】:

  • @donlan - 如果你使用 C++17,你可以在某些情况下使用折叠表达式让事情变得更简单(在这里创造性地思考,你可以使用 , 操作符和折叠表达式)。否则,我不这么认为。
  • 很好的文章参考了。。当我开始阅读“我们可以构建可变参数类型”一节中的类模板元组时,我突然想到:“天哪!这是 Lisp 编程,在编译时,在 C++ ..” ..LOL..
【解决方案6】:

C++ 支持 C 风格的可变参数函数。

但是,大多数 C++ 库都使用另一种习语,例如而'c' printf 函数采用可变参数,c++ cout 对象使用&lt;&lt; 重载来解决类型安全和ADT(可能以实现简单为代价)。

【讨论】:

  • 此外,这似乎只适用于像打印这样的函数,在这种情况下,您实际上在每个 arg 中都有一个单个参数函数的迭代。否则,您只是在初始化一个列表并在std::initializer_lists 上传递该列表以供结束......这已经在一项简单的任务中引入了巨大的复杂性。
【解决方案7】:

除了可变参数或重载之外,您还可以考虑在 std::vector 或其他容器(例如 std::map )中聚合您的参数。像这样的:

template <typename T> void f(std::vector<T> const&);
std::vector<int> my_args;
my_args.push_back(1);
my_args.push_back(2);
f(my_args);

通过这种方式,您将获得类型安全,并且这些可变参数的逻辑含义将显而易见。

当然,这种方法可能会出现性能问题,但您不必担心这些问题,除非您确定自己无法付出代价。这是一种 C++ 的“Pythonic”方法......

【讨论】:

  • 更清洁的是不强制执行向量。而是使用模板参数指定 STL 样式的集合,然后使用参数的 begin 和 end 方法遍历它。这样你就可以使用 std::vector、c++11 的 std::array、std::initializer_list 甚至是你自己的集合。
  • @JensÅkerblom 我同意,但这种选择应该针对手头的问题进行分析,以避免过度设计。由于这是 API 签名的问题,因此了解最大灵活性和意图/可用性/可维护性等清晰度之间的权衡非常重要。
【解决方案8】:

唯一的方法是使用 C 风格的变量参数,如 here 所述。请注意,这不是推荐的做法,因为它不安全且容易出错。

【讨论】:

  • 容易出错我假设您的意思是可能非常非常危险?尤其是在处理不受信任的输入时。
  • 是的,但是因为类型安全问题。想想常规 printf 可能存在的所有问题:格式字符串与传递的参数不匹配,等等。 printf 使用相同的技术,顺便说一句。
【解决方案9】:

如果不使用 C 风格的可变参数 (...),没有标准的 C++ 方法可以做到这一点。

当然有一些默认参数“看起来”像可变数量的参数,具体取决于上下文:

void myfunc( int i = 0, int j = 1, int k = 2 );

// other code...

myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );

所有四个函数调用都使用不同数量的参数调用myfunc。如果没有给出,则使用默认参数。但是请注意,您只能省略尾随参数。没有办法,例如省略i而只给出j

【讨论】:

    【解决方案10】:

    您可能需要重载或默认参数 - 使用默认参数定义相同的函数:

    void doStuff( int a, double termstator = 1.0, bool useFlag = true )
    {
       // stuff
    }
    
    void doStuff( double std_termstator )
    {
       // assume the user always wants '1' for the a param
       return doStuff( 1, std_termstator );
    }
    

    这将允许您使用四种不同调用之一来调用该方法:

    doStuff( 1 );
    doStuff( 2, 2.5 );
    doStuff( 1, 1.0, false );
    doStuff( 6.72 );
    

    ...或者您可能正在寻找 C 中的 v_args 调用约定。

    【讨论】:

      【解决方案11】:

      使用可变参数模板,重现 console.log 的示例,如 JavaScript 中所示:

      Console console;
      console.log("bunch", "of", "arguments");
      console.warn("or some numbers:", 1, 2, 3);
      console.error("just a prank", "bro");
      

      文件名,例如js_console.h:

      #include <iostream>
      #include <utility>
      
      class Console {
      protected:
          template <typename T>
          void log_argument(T t) {
              std::cout << t << " ";
          }
      public:
          template <typename... Args>
          void log(Args&&... args) {
              int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
              cout << endl;
          }
      
          template <typename... Args>
          void warn(Args&&... args) {
              cout << "WARNING: ";
              int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
              cout << endl;
          }
      
          template <typename... Args>
          void error(Args&&... args) {
              cout << "ERROR: ";
              int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
              cout << endl;
          }
      };
      

      【讨论】:

        【解决方案12】:

        支持颜色代码的 C++ 11

        • 通用且适用于所有数据类型
        • 像 JavaScript console.log(1,"23")
        • 支持信息、警告、错误的颜色代码。
        • 示例:
        #pragma once
        #include <iostream>
        #include <string>
        
        const std::string RED = "\e[0;91m";
        const std::string BLUE = "\e[0;96m";
        const std::string YELLOW = "\e[0;93m";
        
        class Logger {
        private:
          enum class Severity { INFO, WARN, ERROR };
        
          static void print_colored(const char *log, Severity severity) {
            const char *color_code = nullptr;
        
            switch (severity) {
            case Severity::INFO:
              color_code = BLUE.c_str();
              break;
            case Severity::WARN:
              color_code = YELLOW.c_str();
              break;
            case Severity::ERROR:
              color_code = RED.c_str();
              break;
            }
        
            std::cout << "\033" << color_code << log << "\033[0m -- ";
          }
        
          template <class Args> static void print_args(Args args) {
            std::cout << args << " ";
          }
        
        public:
          template <class... Args> static void info(Args &&...args) {
            print_colored("[INFO] ", Severity::INFO);
            int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
            std::cout << std::endl;
          }
        
          template <class... Args> static void warn(Args &&...args) {
            print_colored("[WARN] ", Severity::WARN);
            int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
            std::cout << std::endl;
          }
        
          template <class... Args> static void error(Args &&...args) {
            print_colored("[ERROR]", Severity::ERROR);
            int dummy[] = {0, ((void)print_args(std::forward<Args>(args)), 0)...};
            std::cout << std::endl;
          }
        };
        
        

        【讨论】:

          【解决方案13】:

          如果你知道将提供的参数数量的范围,你总是可以使用一些函数重载,比如

          f(int a)
              {int res=a; return res;}
          f(int a, int b)
              {int res=a+b; return res;}
          

          等等……

          【讨论】:

            【解决方案14】:

            正如其他人所说,C 风格的可变参数。但是你也可以用默认参数做类似的事情。

            【讨论】:

            • 您能详细说明一下吗?
            • @QPaysTaxes:有关如何使用默认参数执行此操作的示例,请查看 Zoltan 的答案。
            【解决方案15】:
            // spawn: allocate and initialize (a simple function)
            template<typename T>
            T * spawn(size_t n, ...){
              T * arr = new T[n];
              va_list ap;
              va_start(ap, n);
              for (size_t i = 0; i < n; i++)
                T[i] = va_arg(ap,T);
              return arr;
            }
            
            

            用户写道:

            auto arr = spawn<float> (3, 0.1,0.2,0.3);
            

            从语义上讲,这看起来和感觉完全像一个 n 参数函数。在引擎盖下,您可能会以一种或另一种方式打开它。

            【讨论】:

              【解决方案16】:

              现在可以...使用 boost any 和模板 在这种情况下,参数类型可以混合使用

              #include <boost/any.hpp>
              #include <iostream>
              
              #include <vector>
              using boost::any_cast;
              
              template <typename T, typename... Types> 
              void Alert(T var1,Types... var2) 
              { 
              
                  std::vector<boost::any> a(  {var1,var2...});
              
                  for (int i = 0; i < a.size();i++)
                  {
              
                  if (a[i].type() == typeid(int))
                  {
                      std::cout << "int "  << boost::any_cast<int> (a[i]) << std::endl;
                  }
                  if (a[i].type() == typeid(double))
                  {
                      std::cout << "double "  << boost::any_cast<double> (a[i]) << std::endl;
                  }
                  if (a[i].type() == typeid(const char*))
                  {
                      std::cout << "char* " << boost::any_cast<const char*> (a[i]) <<std::endl;
                  }
                  // etc
                  }
              
              } 
              
              
              void main()
              {
                  Alert("something",0,0,0.3);
              }
              

              【讨论】:

              • 为什么要添加额外的依赖项? C++17 为我们提供了一个很好的类std::any
              【解决方案17】:

              如果所有参数都是 const 且类型相同,我们也可以使用 initializer_list

              【讨论】:

                【解决方案18】:
                int fun(int n_args, ...) {
                   int *p = &n_args; 
                   int s = sizeof(int);
                   p += s + s - 1;
                   for(int i = 0; i < n_args; i++) {
                     printf("A1 %d!\n", *p);
                     p += 2;
                   }
                }
                

                普通版

                【讨论】:

                • 以及在 x86 以外的任何东西上都不起作用的未定义行为。
                • 写汇编比这还要好。
                猜你喜欢
                • 2017-08-04
                • 1970-01-01
                • 2010-12-07
                • 1970-01-01
                • 2017-11-24
                • 1970-01-01
                • 1970-01-01
                • 2012-08-14
                相关资源
                最近更新 更多