使用初始化器列表设置insert

std::set insert with initialiser lists

本文关键字:设置 insert 列表 初始化      更新时间:2023-10-16

我有这个简单的代码:

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

这里的经验法则是initializer_list<X>重载比其他重载更受欢迎

首先,from [over.ics.list]

如果形参类型为std::initializer_list<X>,且初始化列表的所有元素都可以当隐式转换为X时,隐式转换序列是转换an所需的最差转换元素转换为X,或者如果初始化列表没有元素,则进行标识转换。这种转换可以即使在调用初始化列表构造函数的上下文中,也是用户定义的转换。

And, from [over.ics.rank]:

如果

列表初始化序列L1是比列表初始化序列L2更好的转换序列- L1转换为std::initializer_list<X>X和L2不[…]

我们有两个相关的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}

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