C ++中可变数量的参数?

时间:2009-11-01 18:25:02

标签: c++ variadic-functions

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

17 个答案:

答案 0 :(得分:331)

C ++ 11 中,您有两个新选项, Alternatives部分中的Variadic functions参考页面指出:

  
      
  • Variadic模板也可用于创建可变数量的函数   参数。他们往往是更好的选择,因为他们没有施加限制   参数的类型,不执行整数和浮点促销,以及   是安全的。 (自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

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 ;
} 

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

  

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

答案 1 :(得分:130)

你可能不应该,你可以用更安全,更简单的方式做你想做的事。从技术上讲,在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<<())或默认参数等。这些都更安全:编译器会更多地了解您尝试执行的操作,因此更多的场合它可以在你断腿之前阻止你。

答案 2 :(得分:18)

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

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

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

列出初始化程序FTW!

答案 3 :(得分:17)

在C ++ 11中,有一种方法可以使用变量参数模板,这种模板可以实现一种非常优雅且类型安全的方式来获取可变参数函数。 Bjarne本人在printf using variable argument templates中给出了C++11FAQ的一个很好的例子。

就个人而言,我认为这很优雅,我甚至不会在C ++中使用变量参数函数,直到该编译器支持C ++ 11变量参数模板。

答案 4 :(得分:15)

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

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

答案 5 :(得分:13)

除了v​​arargs或重载之外,您可以考虑在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”方法......

答案 6 :(得分:9)

C ++ 17解决方案:完整类型安全+不错的调用语法

由于在C ++ 11中引入了可变参数模板,在C ++中引入了表达式17,因此可以定义一个模板函数,该函数在被调用者站点可以调用,就好像它是一个varidic函数,但是优点:

  • 是强类型安全的;
  • 没有参数数量的运行时信息,或没有使用“停止”参数。

以下是混合参数类型的示例

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

答案 7 :(得分:8)

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

答案 8 :(得分:7)

如果不采用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

答案 9 :(得分:4)

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

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调用约定。

答案 10 :(得分:2)

如果您知道将提供的参数数量范围,您可以随时使用某些函数重载,例如

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

依旧......

答案 11 :(得分:2)

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

答案 12 :(得分:1)

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;
   }
}

普通版

答案 13 :(得分:1)

使用可变参数模板,例如再现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;
    }
};

答案 14 :(得分:0)

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

答案 15 :(得分:0)

现在有可能...使用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);
}

答案 16 :(得分:0)

// 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参数函数。在引擎盖下,您可以用另一种方法打开包装。