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


当前回答

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

由于在c++ 11中引入了变进模板,在c++ 17中引入了fold表达式,因此可以在调用端定义一个模板函数,它可以像一个变进函数一样被调用,但优点是:

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

下面是一个混合参数类型的例子

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!");
                                                                                              ^

更多信息:

可变参数模板,也称为参数包(自c++ 11起)- cppreference.com。 折叠表达式(自c++ 17开始) 查看coliru上的完整程序演示。

其他回答

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++支持C风格的变进函数。

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

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

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