当初始化器列表可用时,为什么现在使用可变参数? [英] Why use variadic arguments now when initializer lists are available?

查看:109
本文介绍了当初始化器列表可用时,为什么现在使用可变参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直想知道可变参数比初始值设定项列表有什么优势。两者都具有相同的功能-将无限数量的参数传递给函数。

I've been wondering what are the advantages of variadic arguments over initializer lists. Both offer the same ability - to pass indefinite number of arguments to a function.

我个人认为初始化列表更为优雅。语法不太尴尬。

What I personally think is initializer lists are a little more elegant. Syntax is less awkward.

此外,随着参数数量的增加,初始化列表的性能似乎也大大提高。

Also, it appears that initializer lists have significantly better performance as the number of arguments grows.

那么,除了在C语言中使用可变参数的可能性之外,我还缺少什么?

So what am I missing, besides the possibility to use use variadic arguments in C as well?

推荐答案

如果可变参数您指的是省略号(例如 void foo(...)),则这些变量或多或少地由可变模板而不是通过初始化程序列表-与SFINAE一起使用以实现(例如)类型特征或C兼容性时,省略号仍然会有一些用例,但是我将在这里讨论普通用例。

If by variadic arguments you mean the ellipses (as in void foo(...)), then those are made more or less obsolete by variadic templates rather than by initializer lists - there still could be some use cases for the ellipses when working with SFINAE to implement (for instance) type traits, or for C compatibility, but I will talk about ordinary use cases here.

可变参数模板实际上允许参数包使用不同类型(实际上是 any 类型),而初始值设定项列表的值必须可转换为的基础类型。初始化列表(不允许缩小转换):

Variadic templates, in fact, allow different types for the argument pack (in fact, any type), while the values of an initializer lists must be convertible to the underlying type of the initalizer list (and narrowing conversions are not allowed):

#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
}

因此,初始化类型列表在类型推断时使用较少是必需的,除非参数的类型确实确实是同质的。另一方面,可变参数模板提供椭圆可变参数列表的类型安全版本。

Because of this, initializer lists are less often used when type deduction is required, unless the type of the arguments is indeed meant to be homogenous. Variadic templates, on the other hand, provide a type-safe version of the ellipses variadic argument list.

此外,调用一个需要初始化程序列表需要将参数括在大括号中,而采用可变参数包的函数则不是这样。

Also, invoking a function that takes an initializer list requires enclosing the arguments in a pair of braces, which is not the case for a function taking a variadic argument pack.

最后(嗯,还有其他区别,但这些与您的问题更相关),初始化列表中的值是 const 对象。根据C ++ 11标准的第18.9 / 1段:

Finally (well, there are other differences, but these are the ones more relevant to your question), values in an initializer lists are const objects. Per Paragraph 18.9/1 of the C++11 Standard:


initializer_list< E>类型的对象提供对类型为 const E 的对象数组的访问。 [...]复制初始化程序列表并不会复制
而不复制基础元素。 [...]

An object of type initializer_list<E> provides access to an array of objects of type const E. [...] Copying an initializer list does not copy the underlying elements. [...]

这意味着尽管不可复制的类型可以移入初始化列表,但不能将其移出它。此限制可能满​​足或可能不满足程序的要求,但通常使初始化程序列出用于保存不可复制类型的限制选择。

This means that although non-copyable types can be moved into an initializer lists, they cannot be moved out of it. This limitation may or may not meet a program's requirement, but generally makes initializer lists a limiting choice for holding non-copyable types.

更一般而言,无论如何,使用对象时作为初始化列表的一个元素,我们将对其进行复制(如果是左值)或将其移开(如果它是右值):

More generally, anyway, when using an object as an element of an initializer list, we will either make a copy of it (if it is an lvalue) or move away from it (if it is an rvalue):

#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
}

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

In other words, initializer lists cannot be used to pass arguments by reference (*), let alone performing perfect forwarding:

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 ++标准库的所有其他容器不同)确实具有引用语义 ,因此尽管元素的复制/移动是在将元素插入初始化列表时执行的操作,复制初始化列表本身不会导致所包含对象的任何复制/移动(如上文引用的标准段落所述):


(*) It must be noted, however, that initializer lists (unlike all other containers of the C++ Standard Library) do have reference semantics, so although a copy/move of the elements is performed when inserting elements into an initializer list, copying the initializer list itself won't cause any copy/move of the contained objects (as mentioned in the paragraph of the Standard quoted above):

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
}

这篇关于当初始化器列表可用时,为什么现在使用可变参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆