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 list
到 insert
。对默认参数所做的是创建两个对象,每个对象 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>
的重载。 1
和 2
都不能隐式转换为 D1
,因此重载是不可行的。现在考虑 D1&&
重载。因为我们可以用那个初始化列表构造一个 D1
,那就是选择的重载,我们最终得到一个元素:D1{1, 2}
.
但是,在这种情况下:
s2.insert({1, 2});
1
和 2
都可以 隐式转换为 D2
,这要归功于 D2
中的默认参数'构造器。所以 initializer_list<D2>
重载是可行的。 D2&&
重载也是可行的,但 initializer_list
转换序列是更好的转换序列,因此它是首选。这给了我们 两个 元素,D2{1}
和 D2{2}
.
我有这个简单的代码:
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 list
到 insert
。对默认参数所做的是创建两个对象,每个对象 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 toX
, the implicit conversion sequence is the worst conversion necessary to convert an element of the list toX
, 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 tostd::initializer_list<X>
for someX
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>
的重载。 1
和 2
都不能隐式转换为 D1
,因此重载是不可行的。现在考虑 D1&&
重载。因为我们可以用那个初始化列表构造一个 D1
,那就是选择的重载,我们最终得到一个元素:D1{1, 2}
.
但是,在这种情况下:
s2.insert({1, 2});
1
和 2
都可以 隐式转换为 D2
,这要归功于 D2
中的默认参数'构造器。所以 initializer_list<D2>
重载是可行的。 D2&&
重载也是可行的,但 initializer_list
转换序列是更好的转换序列,因此它是首选。这给了我们 两个 元素,D2{1}
和 D2{2}
.