关于可变参数函数:C ++中可变数量的参数?

Variable number of arguments in C++?

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


在C++ 11中,有两个新选项,因为可选部分中的变量函数引用页表示:

  • Variadic templates can also be used to create functions that take variable number of
    arguments. They are often the better choice because they do not impose restrictions on
    the types of the arguments, do not perform integral and floating-point promotions, and
    are type safe. (since C++11)
  • If all variable arguments share a common type, a std::initializer_list provides a
    convenient mechanism (albeit with a different syntax) for accessing variable arguments.

下面是一个例子,展示了这两种备选方案(请现场观看):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#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,我们可以使用漂亮的函数magic变量来显示函数的类型签名,这有助于理解正在发生的事情。例如,使用:

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

对于示例中的变量函数,是否会在后面产生int(请参见live):

1
2
3
4
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。

更新前C++ 11

预C++ 11 STD::IpdialIZELSILL列表将是STD::向量或其他标准容器中的一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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 ) ;
}

变量模板的另一种选择是变量函数,尽管它们不是类型安全的,而且通常容易出错,并且使用起来不安全,但是唯一的其他可能选择是使用默认参数,尽管使用起来有限。下面的示例是链接引用中示例代码的修改版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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 << '
'
;
        } else if (*fmt == 's') {
            char * s = va_arg(args, char*);
            std::cout << s << '
'
;
        }
        ++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 ;
}

使用变量函数也可以在您可以通过的参数中进行限制,这在EDCOX1第2函数调用第7段中的C++标准草案中详细说明:

When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.7). The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9), the behavior is undefined. [...]


你可能不应该这样做,而且你可以用一种更安全、更简单的方式来做你想做的事情。从技术上讲,在C语言中使用可变数量的参数,包括stdarg.h。从中可以得到va_list类型,以及在其上运行的三个函数,分别称为va_start()va_arg()va_end()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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<<())或默认参数等。这些都比较安全:编译器会更加了解您要做的事情,因此在您失去控制之前,它会在更多情况下阻止您。


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

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

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

列表初始值设定项ftw!


在C++ 11中,有一种方法来执行变量参数模板,这导致了一种非常优雅和类型安全的方法来拥有变量参数函数。Bjarne自己给出了在C++11FAQ中使用变量参数模板的PrimTF的一个很好的例子。

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


C风格的变量函数在C++中被支持。

然而,大多数C++库使用另一个习语,而EDCOX1的0个函数使用可变的参数。EDCOX1×1的对象使用EDCOX1×2的重载来解决类型安全和ADTs(可能以实现简单为代价)。


STD::向量或其他容器(STD::MAP),除了VARARGS或重载,您可以考虑聚合参数。像这样:

1
2
3
4
5
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);

这样,您将获得类型安全性,并且这些可变参数的逻辑含义将是显而易见的。

当然,这种方法可能会有性能问题,但是您不应该担心它们,除非您确定您不能支付价格。它是一种"Pythic"的C++方法。


C++ 17解决方案:全类型安全+漂亮调用语法

由于在C++ 11中引入了可变模板和C++ 17中的折叠表达式,所以可以定义模板函数,它在被调用的站点上是可调用的,好像它是一个VARIDIC函数,但具有以下优点:

  • 类型安全性强;
  • 不使用参数数量的运行时信息,也不使用"stop"参数。

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

1
2
3
4
5
6
7
template<class... Args>
void print(Args... args)
{
    (std::cout << ... << args) <<"
"
;
}
print(1, ':'," Hello", ',',"","World!");

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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::value, void>>
void print_same_type(Head head, Tail... tail)
{
    std::cout << head;
    (std::cout << ... << tail) <<"
"
;
}
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!");
                                                                                              ^

更多信息:

  • 变量模板,也称为参数包参数包(因为C++ 11)-CPAPYCENCE.COM。
  • 折叠表达式折叠表达式(因为C++ 17)-CPAPYCENCE.COM。
  • 在Coliru上看到完整的程序演示。

  • 唯一的方法是使用C样式变量参数,如本文所述。请注意,这不是推荐的实践,因为它不具有类型安全性和易出错性。


    在不使用C风格VARARGS(EDCOX1×8)的情况下,没有标准的C++方式来实现这一点。

    当然,根据上下文的不同,有一些默认参数看起来像变量个数的参数:

    1
    2
    3
    4
    5
    6
    7
    8
    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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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 );
    }

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

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

    …或者您可以从C中查找V参数调用约定。


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

    1
    2
    3
    4
    f(int a)
        {int res=a; return res;}
    f(int a, int b)
        {int res=a+b; return res;}

    等等……


    正如其他人所说,C型变容二极管。但您也可以对默认参数执行类似的操作。


    使用变量模板,例如复制console.log,如javascript中所示:

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

    文件名,如js_console.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #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;
        }
    };


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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!
    "
    , *p);
         p += 2;
       }
    }

    普通版


    如果所有参数都是常量且类型相同,我们也可以使用初始值设定项列表。