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

26 浏览
0 Comments

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

如何编写一个接受可变数量参数的函数?这是否可能,如何实现?

0
0 Comments

C++17中的可变参数模板和折叠表达式的引入,使得我们可以定义一个模板函数,使其在调用时可以像可变参数函数一样被调用,但具有以下优势:

- 强类型安全;

- 在不使用运行时信息或“停止”参数的情况下工作。

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

template
void print(Args... args)
{
    (std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");

还有另一个示例,对所有参数强制进行类型匹配:

#include  // enable_if, conjuction
template
using are_same = std::conjunction...>;
template::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. 可变参数模板,也称为参数包(自C++11起)- cppreference.com。

2. 折叠表达式(自C++17起)- cppreference.com。

3. 在coliru上查看完整的程序演示。

我们可以遍历args吗?

是的,但这是另一个问题。

0
0 Comments

在C++11中,出现了两种新的选择,如“Variadic arguments”参考页面在“Alternatives”部分所述:

- 可变模板也可以用于创建接受可变数量参数的函数。它们通常是更好的选择,因为它们不会对参数的类型施加限制,不会执行整数和浮点数提升,并且是类型安全的(自C++11起)。

- 如果所有可变参数共享相同的类型,则std::initializer_list提供了一种方便的机制(尽管使用了不同的语法)来访问可变参数。

下面是一个显示两种选择的示例:

#include 
#include 
#include 
template 
void func(T t) 
{
    std::cout << t << std::endl ;
}
template
void func(T t, Args... args) // 递归可变参数函数
{
    std::cout << t << std::endl ;
    func(args...) ;
}
template 
void func2( std::initializer_list 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 <

在示例的可变函数中,将产生以下结果:

void func(T, Args...) [T = int, Args = >]: 1
void func(T, Args...) [T = double, Args = >]: 2.5
void func(T, Args...) [T = char, Args = >]: a
void func(T) [T = std::basic_string]: Hello

在Visual Studio中,可以使用FUNCSIG。

在C++11之前,std::initializer_list的替代品将是std::vector或其他标准容器之一。下面是一个修改的示例代码:

#include 
#include 
#include 
template 
void func1( std::vector vec )
{
    for( typename std::vector::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 v1( arr1, arr1+4 ) ;
    std::vector v2( arr2, arr2+2 ) ;
    func1( v1 ) ;
    func1( v2 ) ;
}

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

#include 
#include 
#include 
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节“Function call”的第7段中有详细说明:

当给定参数没有参数时,参数将以一种使接收函数能够通过调用va_arg(18.7)获得参数值的方式传递。对参数表达式执行左值到右值(4.1)、数组到指针(4.2)和函数到指针(4.3)的标准转换。完成这些转换后,如果参数没有算术、枚举、指针、成员指针或类类型,则程序是非法的。如果参数具有非POD类类型(clause 9),行为是未定义的。[...]

以上是关于“Variable number of arguments in C++?”问题的原因和解决方法的整理。

0
0 Comments

C++中是否支持可变参数的问题,出现的原因是因为C++中的可变参数使用起来较为复杂、不安全,并且与我们所要实现的概念无关。解决方法是考虑使用重载、继承/多态、建造者模式(比如流中的operator<<())或默认参数等更安全的方法。这些方法能让编译器更好地了解你所要实现的功能,从而可以在你犯错之前阻止你。

在C++中,不需要在"..."语法之前提供至少一个参数,虽然这不是语言或库的要求,但标准库并没有提供你告诉它列表长度的方法。你需要调用者提供这个信息,或者自己找到方法来确定列表的长度。例如,在printf()函数中,函数解析字符串参数以查找特殊标记,从而确定变长参数列表中应该期望多少额外参数。

在C++中,你应该使用而不是

在使用可变参数实现max(int,int,int...)函数时,可以选择使用数组来实现。数组可能不适合我,因为我无法动态创建和传递数组。对于max函数,你应该使用可变参数模板。

在编程中,清晰度比安全性更重要,因此为什么要把正确的答案包含在观点中?虽然使用初始化列表实现可变参数可能会引入太多的语义复杂性和冗余,但缺乏冗余是更重要的。无论如何,技术细节是我们获得报酬的原因。在计算有效数值导数、积分或设置托卡马克核聚变模拟中的PDEs等方面,有大量的技术细节。函数接口中的噪音是高级用户的瓶颈,而不是函数体内部的噪音。这个答案远远优于下面的答案。

0