当初始化列表可用时,为什么现在要使用可变参数呢?
Why use variadic arguments now when initializer lists are available?
我一直想知道可变参数相对于初始化列表有什么优势。两者都提供相同的功能——向函数传递不确定数量的参数。
我个人认为初始化列表更优雅一些。语法不那么别扭了。
而且,随着参数数量的增加,初始化列表的性能也明显提高。
那么,除了在C中使用use可变参数的可能性之外,我还错过了什么?如果可变参数指的是省略号(如void foo(...)
),那么这些省略号或多或少会被可变模板而不是初始化列表所淘汰——在使用SFINAE实现(例如)类型特征或C兼容性时,省略号仍然可能有一些用例,但我将在这里讨论普通用例。
可变模板实际上允许参数包的不同类型(实际上,任何类型),而初始化列表的值必须转换为初始化列表的基础类型(不允许窄化转换):
#include <utility>
template<typename... Ts>
void foo(Ts...) { }
template<typename T>
void bar(std::initializer_list<T>) { }
int main()
{
foo("Hello World!", 3.14, 42); // OK
bar({"Hello World!", 3.14, 42}); // ERROR! Cannot deduce T
}
因此,除非实参的类型确实是同质的,否则在需要类型演绎时很少使用初始化列表。另一方面,可变参数模板提供了一个类型安全的版本的省略号可变参数列表。
同样,调用接受初始化列表的函数需要将实参括在一对花括号中,而接受可变参数包的函数则不是这样。
最后(好吧,还有其他的区别,但这些与你的问题更相关),初始化列表中的值是const
对象。c++ 11标准第18.9/1段:
类型为
initializer_list<E>
的对象提供了对类型为const E
的对象数组的访问。[…复制初始化列表可以而不是复制底层元素。[…]
这意味着尽管不可复制类型可以移动到初始化列表中,但不能将其移出。这个限制可能满足也可能不满足程序的要求,但通常使初始化列表成为保存不可复制类型的限制选择。
更一般地说,无论如何,当使用对象作为初始化列表的元素时,我们要么复制它(如果它是左值),要么移开它(如果它是右值):
#include <utility>
#include <iostream>
struct X
{
X() { }
X(X const &x) { std::cout << "X(const&)" << std::endl; }
X(X&&) { std::cout << "X(X&&)" << std::endl; }
};
void foo(std::initializer_list<X> const& l) { }
int main()
{
X x, y, z, w;
foo({x, y, z, std::move(w)}); // Will print "X(X const&)" three times
// and "X(X&&)" once
}
换句话说,初始化列表不能用于通过引用(*)传递参数,更不用说执行完美的转发了:
template<typename... Ts>
void bar(Ts&&... args)
{
std::cout << "bar(Ts&&...)" << std::endl;
// Possibly do perfect forwarding here and pass the
// arguments to another function...
}
int main()
{
X x, y, z, w;
bar(x, y, z, std::move(w)); // Will only print "bar(Ts&&...)"
}
然而,必须注意的是,初始化列表(与c++标准库的所有其他容器不同)确实具有引用语义,因此,尽管在向初始化列表插入元素时执行元素的复制/移动,但复制初始化列表本身不会导致所包含对象的任何复制/移动(如上面引用的标准段所述):
int main()
{
X x, y, z, w;
auto l1 = {x, y, z, std::move(w)}; // Will print "X(X const&)" three times
// and "X(X&&)" once
auto l2 = l1; // Will print nothing
}
简单地说,C风格的可变变量函数在编译时产生的代码比c++风格的可变变量模板少,所以如果你担心二进制大小或指令缓存压力,你应该考虑用可变变量来实现你的功能,而不是作为模板。
然而,可变变量模板明显更安全,并且产生更多可用的错误消息,因此您通常会想要用内联可变变量模板包装行外的可变变量函数,并让用户调用该模板。
- 在不传递参数数量且只有3个点的情况下,如何使用变差函数
- 如何使用可变参数模板强制转换每个变体类型
- 关于如何在具有单个参数的变体构造中选择替代方案?
- 调用参数排列不变函数 f(i++, i++)
- 参数归纳与标准::变体
- 模板化回调参数的逆变,如 C# 中的逆变
- 如何在没有参数包的情况下编写变差函数
- 通过具有嵌套类的工厂类获取多个变异类模板参数包
- 获取模板参数的成员变量值列表
- 保留短 lambda 用作函数的中间参数,使用 clang 格式保持不变
- 如何定义变体<x,y,z>提取模板参数的子类型
- 正确对齐内存模板,参数顺序不变
- 递归中不同参数类型的变元模板函数
- 通过函数指针传递给变差函数的参数会更改其值
- 提升预定义为带有参数的全局 lambda 的变体访问者
- 使用可变参数模板参数提升变体访问者
- boost ::变体 - 为什么模板参数比const字符串参数具有更高的优先级
- 将变参数包中的值加载到临时数组中
- 使用额外参数提升变体访客
- 从变长参数列表中提取std::string