我怎么能写一个函数,接受可变数量的参数?这可能吗?怎么可能?


当前回答

在c++ 11中,你有两个新的选项,正如可变参数参考页中alternative部分所述:

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

下面是一个展示这两种选择的例子(看现场):

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

如果您正在使用gcc或clang,我们可以使用PRETTY_FUNCTION魔术变量来显示函数的类型签名,这有助于理解正在发生的事情。例如:

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

对于例子中的可变函数,结果如下(见现场):

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或其他标准容器之一:

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

可变变量模板的替代方法是可变变量函数,尽管它们不是类型安全的,而且通常容易出错,使用起来不安全,但唯一的其他潜在替代方法是使用默认参数,尽管它的用途有限。下面的例子是链接参考中样例代码的修改版本:

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

使用可变值函数也会在参数中带来限制,这在c++标准草案5.2.2节函数调用第7段中有详细说明:

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风格的可变参数(…),就没有标准的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。

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

普通的版本

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

在c++ 11中,你有两个新的选项,正如可变参数参考页中alternative部分所述:

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

下面是一个展示这两种选择的例子(看现场):

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

如果您正在使用gcc或clang,我们可以使用PRETTY_FUNCTION魔术变量来显示函数的类型签名,这有助于理解正在发生的事情。例如:

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

对于例子中的可变函数,结果如下(见现场):

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或其他标准容器之一:

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

可变变量模板的替代方法是可变变量函数,尽管它们不是类型安全的,而且通常容易出错,使用起来不安全,但唯一的其他潜在替代方法是使用默认参数,尽管它的用途有限。下面的例子是链接参考中样例代码的修改版本:

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

使用可变值函数也会在参数中带来限制,这在c++标准草案5.2.2节函数调用第7段中有详细说明:

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. [...]

// 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参数函数。在引擎盖下,你可能会以这样或那样的方式打开它。