为什么C++11中存在std::initializer_list构造函数重载规则?

4
我似乎想不出也找不到以下代码的理由:

我无法为以下代码找到合理的解释:

std::vector<int> a{1,2} // calls (7)
std::vector<int> a(1,2) // calls (2)

// constructors from http://en.cppreference.com/w/cpp/container/vector/vector
vector( std::initializer_list<T> init, 
        const Allocator& alloc = Allocator() ); // (7)

explicit vector( size_type count, 
                 const T& value = T(),
                 const Allocator& alloc = Allocator()); // (2)

根据使用的构造方法({} vs ()),不同的函数被调用,这对我来说似乎非常违反直觉。为什么std::initializer_list比其他完全匹配给定参数的函数更受欢迎呢?我知道C++11中上述第二个构造函数已经被弃用,可能是因为这个改变,但我仍然无法理解为什么会这样。我唯一能看到这种行为的好处是,您可以使用特定值初始化容器并仅需要一对大括号;std::vector<int> a{1,2}std::vector<int> a{{1,2}}相比。但至少对我来说,这肯定不能抵消这种行为所带来的模糊性和重载分辨率的变化。根据Scott Meyers在Effective Modern C++中的说法,std::make_uniquestd::make_shared需要在其接口中明确声明使用哪种构造形式(因为涉及到重载分辨率)。这对我来说似乎很荒谬。
我承认我可能漏掉了什么,但我不确定具体是什么。请注意,我只是以std::vector为例,我询问的是这个特性的一般情况。

2
如果 std::vector<int> a{1,2,3};, std::vector<int> a{1,2,3,4};, std::vector<int> a{1,2,3,4,5}; 都调用初始化列表构造函数,但 std::vector<int> a{1,2}; 却没有,那么会感到同样奇怪。 - T.C.
正因为如此,我认为应该使用std::vector<int> a{{1,2,3}}而不是优先考虑std::initializer_list构造函数。 - user1520427
另外,我不知道你为什么认为#2已经过时了。它并没有。 - T.C.
@T.C. 我没有权威的来源来证明这个废弃的说法,但我注意到它在http://en.cppreference.com/w/cpp/container/vector/vector中被列为“直到C++11”。我理解为它已经被废弃了。 - user1520427
2
啊,C++03的签名在C++11中被分成了两个。原始设计选择的理由可以在http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2100.pdf中找到。 - T.C.
1
@user1520427 在C++11中,已经移除了默认参数,并添加了一个接受计数的构造函数。它们一起模拟了之前的单个构造函数。我相信这个改变是为了让你能够使用非可复制类型的“count”实例来初始化向量。 - Praetorian
1个回答

3
这在 C++11 中很有趣:原因是花括号参数没有类型。但是有一个例外,为什么它存在对我来说不清楚: "auto" 变量是唯一允许隐式将花括号参数作为初始化列表处理的变量。但如果您具有自动函数类型,则不允许返回此初始化列表。
现在,您是对的:初始化列表的好处在于可以使用特定值初始化容器。这是一项巨大的改进,非常值得!
在初始化列表之前,制作一个模板,使您可以使用不同的值初始化容器中的每个类需要使用严格的解决方案,例如接收带有每个值的 std::vector,或构建一个“空”模板类,然后 punching 每个值。
另一件事是,初始化列表允许您以比使用从 C 导入的可怕的 <cstdarg> 更安全的方式创建 C++ 可变函数。 (尽管可变模板在此方面做得更好)
想要稍微尝试一下组合吗?
#include <iostream>
#include <vector>
#include <typeinfo>

using namespace std;


vector<int> func (const vector<int> &a) { //works
//auto func (const vector<int> &a) -> vector<int> { //works
//auto func (const vector<int> &a) { //don't even compile, "returns a initializer list" error
    for (int i: a) {
        cout << "My " << i << endl;
    }

    return {20 , 30};
}

int main()
{
    //play with anonymous functions
    auto y = [ ](vector<int> e) { return e; }; //works
    vector<int> x = y({20, 30});
    //auto y = [ ](){ return {20, 30}; }; //don't even compile, "returns a initializer list" error
    //vector<int> x = y();

    //play with initialization
    //vector<int> x = {2,2,20,30}; //works
    //vector<int> x{2,2,20,30}; //works
    //auto x = vector<int>{2,2,20,30}; //works
    //Bellow, a common mistake of people initializing a int to a auto, like auto x = { 1 }
    //auto x = {2,2,20,30}; //wrong, but compiles, its a initializer list
    //auto x{2,2,20,30}; //wrong, but compiles, its a initializer list

    //Play with return types
    //vector<int> x = func(vector<int>(2,2));  //works only with vector<int> and auto with trailing type
    //vector<int> x(func(vector<int>(2,2))); //works only with vector<int> and auto with trailing type
    //vector<int> x{func(vector<int>(2,2))}; //works only with vector<int> and auto with trailing type
    //auto x = func(vector<int>(2,2));  //works only with vector<int> and auto with trailing type
    //auto x(func(vector<int>(2,2))); //works only with vector<int> and auto with trailing type

    cout << typeid(x).name() << endl;
    for (int i: x) {
        cout << "My " << i << endl;
    }

    return 0;
}

1
这很有趣,我想这在某种程度上解释了为什么我们最终会出现这种行为(因为括号参数没有类型)。我肯定认为标准委员会没有意识到这个问题是不幸的,他们应该采用“双括号”解决方案,而不是为了重载分辨率而处理std::initializer_list。虽然我看不出他们故意想要这种行为的原因。统一初始化本来可以如此优雅。 - user1520427

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接