std::set 使用初始化列表插入

std::set insert with initialiser lists

我有这个简单的代码:

struct Base
{
    Base(int xx, int yy) : x(xx), y(yy){}
    bool operator<(const Base& b) const {return (x < b.x) || (x==b.x && y < b.y);}

    int x;
    int y;
};

struct D1 : Base
{
    D1(int x, int y) : Base(x, y){}
};

struct D2 : Base
{
    D2(int x = 0, int y = 0) : Base(x, y){}
};

void test()
{
    std::set<D1> s1;
    std::set<D2> s2;

    s1.insert({1, 2});
    s2.insert({1, 2});

    std::cout<<"s1 size:"<<s1.size()<<std::endl<<"Content:"<<std::endl;
    for(auto& v : s1)
    {
        std::cout<<v.x<<" "<<v.y<<std::endl;
    }

    std::cout<<std::endl<<"s2 size:"<<s2.size()<<std::endl<<"Content:"<<std::endl;
    for(auto& v : s2)
    {
        std::cout<<v.x<<" "<<v.y<<std::endl;
    }
}

输出:

s1 size:1
Content: 
1 2

s2 size:2
Content:
1 0
2 0

为什么插入带有默认参数的对象时行为不同?这是错误还是预期的行为?

PS:您可以在此处查看代码:https://ideone.com/UPArOi

这是标准行为。这是因为您在 set 中使用 initialization listinsert。对默认参数所做的是创建两个对象,每个对象 int (使用第二个参数的默认值)。

这里的经验法则是 initializer_list<X> 重载 比其他重载 更受青睐。

首先,来自[over.ics.list]

if the parameter type is std::initializer_list<X> and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X, or if the initializer list has no elements, the identity conversion. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor.

并且,来自 [over.ics.rank]:

List-initialization sequence L1 is a better conversion sequence than list-initialization sequence L2 if
— L1 converts to std::initializer_list<X> for some X and L2 does not [...]

我们有两个相关的重载 std::set::insert:

std::pair<iterator,bool> insert( value_type&& value );
void insert( std::initializer_list<value_type> ilist );

第一次通话:

s1.insert({1, 2});

考虑参数类型 std::initializer_list<D1> 的重载。 12 都不能隐式转换为 D1,因此重载是不可行的。现在考虑 D1&& 重载。因为我们可以用那个初始化列表构造一个 D1 ,那就是选择的重载,我们最终得到一个元素:D1{1, 2}.

但是,在这种情况下:

s2.insert({1, 2});

12 都可以 隐式转换为 D2,这要归功于 D2 中的默认参数'构造器。所以 initializer_list<D2> 重载是可行的。 D2&& 重载也是可行的,但 initializer_list 转换序列是更好的转换序列,因此它是首选。这给了我们 两个 元素,D2{1}D2{2}.