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

nun*_*nos 251 c++ variadic-functions

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

Sha*_*our 361

C++ 11中,您有两个新选项,因为Alternatives部分中Variadic函数参考页面指出:

  • 可变参数模板也可用于创建带有可变数量参数的函数.它们通常是更好的选择,因为它们不对参数的类型施加限制,不执行整数和浮点式促销,并且是类型安全的.(自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 } ) ;
} 
Run Code Online (Sandbox Code Playgroud)

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

std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;
Run Code Online (Sandbox Code Playgroud)

将结果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
Run Code Online (Sandbox Code Playgroud)

在Visual Studio中,您可以使用FUNCSIG.

更新Pre C++ 11

Pre 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 ) ;
}
Run Code Online (Sandbox Code Playgroud)

并为替代可变参数模板可变参数的功能,虽然它们不是类型安全和一般容易出错,可能是不安全的使用,但唯一的其他可能的替代办法是使用默认参数,虽然有限制地使用.以下示例是链接引用中示例代码的修改版本:

#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 ;
} 
Run Code Online (Sandbox Code Playgroud)

使用可变参数函数也会对您可以传递的参数进行限制,这在函数调用7段的C++标准草案中有详细说明:5.2.2

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


wil*_*ell 147

你可能不应该,你可以用更安全和更简单的方式做你想做的事.从技术上讲,在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;
}
Run Code Online (Sandbox Code Playgroud)

如果你问我,这是一团糟.它看起来很糟糕,它不安全,并且它充满了技术细节,与你在概念上想要达到的目标无关.相反,考虑使用重载或继承/多态,构建器模式(如在operator<<()流中)或默认参数等.这些都更安全:编译器会更多地了解您尝试执行的操作,因此有更多时间可以停止你在断腿之前

  • 你应该在C++中使用`<cstdarg>`而不是`<stdarg.h>` (10认同)
  • 可变数量的参数非常适合调试或填充某些数组的函数/方法.它也适用于许多数学运算,例如最大值,最小值,总和,平均值......当你不惹它时,它并不是一团糟. (9认同)
  • 据推测,你不能传递对varargs函数的引用,因为编译器不会知道何时通过值传递,何时通过引用,并且因为底层的C宏不一定知道如何处理引用 - 对于什么已经有限制你可以传递一个带有可变参数的C函数,因为像促销规则这样的东西. (7认同)
  • 是否有必要在`...`语法之前提供至少一个参数? (3认同)
  • @Lazer它不是语言或库的要求,但是标准库没有给出告诉列表长度的方法.您需要呼叫者为您提供此信息,或者以某种方式自行解决.例如,在`printf()`的情况下,该函数解析特殊标记的字符串参数,以确定它应该在变量参数列表中预期多少额外的参数. (3认同)
  • 在`...`之前确实需要至少有一个普通参数,您将其名称传递给`va_start`。这是为了允许宏使用该参数的地址来计算其余参数的地址的实现(其他实现可能例如使用内置的某些编译器来访问堆栈或帧指针)。 (2认同)

小智 19

在c ++ 11中你可以做到:

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

foo({"arg1","arg2"});
Run Code Online (Sandbox Code Playgroud)

列表初始化FTW!


Omn*_*ous 18

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

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


YSC*_*YSC 16

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

自从在C++ 11中引入可变参数模板和在C++ 17中引入折叠表达式以来,可以定义一个模板函数,该函数在被调用者站点可被调用,就好像它是一个varidic函数但具有以下优点: :

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

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

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

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

#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!");
                                                                                              ^
Run Code Online (Sandbox Code Playgroud)

更多信息:

  1. 变量模板,也称为参数包参数包(自C++ 11起) - cppreference.com.
  2. 折叠表达式折叠表达式(自C++ 17起) - cppreference.com.
  3. 查看coliru上的完整程序演示.

  • 希望有一天我能读到`template &lt;class Head,class ... Tail,class = std :: enable_if_t &lt;are_same &lt;Head,Tail ...&gt; :: value,void &gt;&gt;` (5认同)
  • @Eladian 将其读作“仅当`Head` 和`Tail...` _are 相同_ 时才启用此功能”,其中“_are 相同_”表示`std::conjunction&lt;std::is_same&lt;Head, Tail&gt;。 ..&gt;`。将最后一个定义读为“`Head` 与所有`Tail...` 相同”。 (4认同)
  • 我们可以循环“args”吗? (4认同)

Wil*_*ill 15

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

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


Fra*_*sco 13

除了varargs或重载之外,您可以考虑在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);
Run Code Online (Sandbox Code Playgroud)

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

当然这种方法可能存在性能问题,但除非您确定无法付出代价,否则不要担心它们.它是一种c ++的"Pythonic"方法......

  • 清洁工将是不强制执行矢量.而是使用指定STL样式集合的模板参数,然后使用参数的begin和end方法迭代它.这样你就可以使用std :: vector <T>,c ++ 11的std :: array <T,N>,std :: initializer_list <T>,甚至可以创建自己的集合. (6认同)
  • @JensÅkerblom我同意,但这是应该针对手头的问题进行分析的选择,以避免过度工程.由于这是API签名的问题,因此了解最大灵活性与意图/可用性/可维护性等的清晰度之间的权衡非常重要. (3认同)

Dav*_*nde 8

唯一的办法是通过使用C风格的变量参数,如所描述这里.请注意,这不是推荐的做法,因为它不是类型安全且容易出错的.


lam*_*345 8

使用可变参数模板,重现console.logJavaScript 中的示例:

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

文件名例如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;
    }
};
Run Code Online (Sandbox Code Playgroud)


Zol*_*oli 7

如果不采用C风格的varargs(...),就没有标准的C++方法可以做到这一点.

当然,默认参数可以根据上下文"看起来"像可变数量的参数:

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

// other code...

myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );
Run Code Online (Sandbox Code Playgroud)

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