重载解析:空括号的赋值
Overload resolution: assignment of empty braces
我写了一些代码 S s;
... s = {};
,希望它最终和 S s = {};
一样。然而它没有。以下示例重现了该问题:
#include <iostream>
struct S
{
S(): a(5) { }
S(int t): a(t) {}
S &operator=(int t) { a = t; return *this; }
S &operator=(S const &t) = default;
int a;
};
int main()
{
S s = {};
S t;
t = {};
std::cout << s.a << '\n';
std::cout << t.a << '\n';
}
输出为:
5
0
我的问题是:
- 为什么这里选择了
operator=(int)
,而不是"ambiguous"或者其他的?
- 有没有不改变
S
的简洁解决方法?
我的意图是 s = S{};
。如果可行,写 s = {};
会很方便。我目前正在使用 s = decltype(s){};
,但我希望避免重复类型或变量名称。
首先,大小写与"int"版本的赋值运算符无关,删除即可。您实际上也可以删除其他赋值运算符,因为它将由编译器生成。 IE 这种类型自动接收 copy/move 构造函数和赋值运算符。 (即它们不被禁止,您只是重复编译器使用显式符号自动执行的操作)
第一种情况
使用复制初始化:
S s = {}; // the default constructor is invoked
这是一个 post 构造复制赋值,但编译器会优化这种简单的情况。你应该使用方向初始化代替:
S s{}; // the default constructor is invoked (as you have it)
注意,你也可以这样写:
S s; // the default constructor is invoked if you have it
第二种情况
你应该写的是复制赋值右边的直接初始化:
t = S{};
此表示法将调用默认构造函数(如果有)或成员的值初始化(只要类型是聚合)。这是相关信息:http://en.cppreference.com/w/cpp/language/value_initialization
Why is operator=(int)
selected here, instead of "ambiguous" or the other one?
{}
到int
是身份转换([over.ics.list]/9). {}
to S
is a user-defined conversion ([over.ics.list]/6)(从技术上讲,它是{}
到const S&
,并经过[over.ics.list]/8 和 [over.ics.ref],然后再回到 [over.ics.list]/6).
先赢。
Is there a tidy workaround?
std::experimental::optional
技巧的变体使 t = {}
总是使 t
为空。
关键是让operator=(int)
成为模板。如果你想接受int
并且只接受int
,那么就变成了
template<class Int, std::enable_if_t<std::is_same<Int, int>{}, int> = 0>
S& operator=(Int t) { a = t; return *this; }
如果你想启用转换,可以使用不同的约束(在这种情况下你可能还想通过引用获取参数)。
要点是,通过将右操作数的类型设为模板参数,您可以阻止 t = {}
使用此重载 - 因为 {}
是非推导上下文。
...without changing S
?
template<class T> T default_constructed_instance_of(const T&) { return {}; }
然后s = default_constructed_instance_of(s);
算吗?
我写了一些代码 S s;
... s = {};
,希望它最终和 S s = {};
一样。然而它没有。以下示例重现了该问题:
#include <iostream>
struct S
{
S(): a(5) { }
S(int t): a(t) {}
S &operator=(int t) { a = t; return *this; }
S &operator=(S const &t) = default;
int a;
};
int main()
{
S s = {};
S t;
t = {};
std::cout << s.a << '\n';
std::cout << t.a << '\n';
}
输出为:
5
0
我的问题是:
- 为什么这里选择了
operator=(int)
,而不是"ambiguous"或者其他的? - 有没有不改变
S
的简洁解决方法?
我的意图是 s = S{};
。如果可行,写 s = {};
会很方便。我目前正在使用 s = decltype(s){};
,但我希望避免重复类型或变量名称。
首先,大小写与"int"版本的赋值运算符无关,删除即可。您实际上也可以删除其他赋值运算符,因为它将由编译器生成。 IE 这种类型自动接收 copy/move 构造函数和赋值运算符。 (即它们不被禁止,您只是重复编译器使用显式符号自动执行的操作)
第一种情况
使用复制初始化:
S s = {}; // the default constructor is invoked
这是一个 post 构造复制赋值,但编译器会优化这种简单的情况。你应该使用方向初始化代替:
S s{}; // the default constructor is invoked (as you have it)
注意,你也可以这样写:
S s; // the default constructor is invoked if you have it
第二种情况
你应该写的是复制赋值右边的直接初始化:
t = S{};
此表示法将调用默认构造函数(如果有)或成员的值初始化(只要类型是聚合)。这是相关信息:http://en.cppreference.com/w/cpp/language/value_initialization
Why is
operator=(int)
selected here, instead of "ambiguous" or the other one?
{}
到int
是身份转换([over.ics.list]/9). {}
to S
is a user-defined conversion ([over.ics.list]/6)(从技术上讲,它是{}
到const S&
,并经过[over.ics.list]/8 和 [over.ics.ref],然后再回到 [over.ics.list]/6).
先赢。
Is there a tidy workaround?
std::experimental::optional
技巧的变体使 t = {}
总是使 t
为空。
关键是让operator=(int)
成为模板。如果你想接受int
并且只接受int
,那么就变成了
template<class Int, std::enable_if_t<std::is_same<Int, int>{}, int> = 0>
S& operator=(Int t) { a = t; return *this; }
如果你想启用转换,可以使用不同的约束(在这种情况下你可能还想通过引用获取参数)。
要点是,通过将右操作数的类型设为模板参数,您可以阻止 t = {}
使用此重载 - 因为 {}
是非推导上下文。
...without changing
S
?
template<class T> T default_constructed_instance_of(const T&) { return {}; }
然后s = default_constructed_instance_of(s);
算吗?