为什么在初始化列表可用时使用可变参数?

dte*_*ech 30 c++ performance variadic-functions initializer-list c++11

我一直想知道可变参数比初始化列表有什么优点.两者都提供相同的能力 - 将无限数量的参数传递给函数.

我个人认为初始化列表更优雅一些.语法不那么尴尬.

此外,随着参数数量的增加,初始化程序列表似乎具有明显更好的性能.

所以除了在C中使用可变参数的可能性之外我还缺少什么?

And*_*owl 50

如果你使用变量参数表示省略号(如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
}
Run Code Online (Sandbox Code Playgroud)

因此,当需要类型推导时,初始化程序列表不常使用,除非参数的类型确实是同质的.另一方面,可变参数模板提供椭圆可变参数列表的类型安全版本.

此外,调用带有初始化列表的函数需要将参数括在一对大括号中,而采用可变参数包的函数则不是这种情况.

最后(好吧,还有其他差异,但这些与您的问题更相关),初始化列表中的值是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
}
Run Code Online (Sandbox Code Playgroud)

换句话说,初始化程序列表不能用于通过引用(*)传递参数,更不用说执行完美转发:

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&&...)"
}
Run Code Online (Sandbox Code Playgroud)

(*)但必须注意,初始化列表(与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
}
Run Code Online (Sandbox Code Playgroud)