鉴于以下计划:
#include <iostream>
#include <string>
using namespace std;
struct GenericType{
operator string(){
return "Hello World";
}
operator int(){
return 111;
}
operator double(){
return 123.4;
}
};
int main(){
int i = GenericType();
string s = GenericType();
double d = GenericType();
cout << i << s << d << endl;
i = GenericType();
s = GenericType(); //This is the troublesome line
d = GenericType();
cout << i << s << d << endl;
}
它在Visual Studio 11上编译,但不是clang或gcc。它有问题,因为它想隐式地从GenericType
转换为int
到char
,但它也可以返回string
,因此存在歧义({ {1}}和operator=(char)
都匹配operator=(string)
)。
然而,复制构造函数很好。
我的问题是:如何在不修改主要内容的情况下解决这种歧义?我需要做些什么才能修改GenericType
来处理这种情况?
答案 0 :(得分:10)
我相信gcc和clang是正确的。
游戏中有两个operator=
重载:
string& operator=(string const& str); // (1)
string& operator=(char ch); // (2)
这两个operator=
重载都需要来自类型GenericType
的参数的用户定义转换。 (1)要求使用转化为string
。 (2)要求使用转化为int
,然后标准转换为char
。
重要的是两个重载都需要用户定义的转换。为了确定其中一个转换是否比另一个更好,我们可以查看重载决策规则,特别是C ++11§13.3.3.2/ 3中的以下规则(为了清晰起见重新格式化):
用户定义的转换序列
U1
是一个比其他用户定义的转换序列U2
更好的转换序列,如果
它们包含相同的用户定义转换函数或构造函数或聚合初始化
- 醇>
U1
的第二个标准转换序列优于U2
的第二个标准转换序列。
请注意,和连接规则的两个部分,因此必须满足这两个部分。规则的第一部分不满足:两个用户定义的转换序列使用不同的用户定义转换函数。
因此,转换都不是更好,而且调用也不明确。
[我没有很好的建议如何在不改变main()
定义的情况下解决问题。隐含的转换通常不是一个好主意;它们有时非常有用,但更频繁地它们可能会导致过载模糊或其他奇怪的重载行为。]
有一个gcc错误报告,其中描述了此问题,并按设计解决:compiler incorrectly diagnoses ambigous operator overload.
答案 1 :(得分:2)
我认为海湾合作委员会错了。在Bjarne Stroustrup的“The C ++ Programming Language”一书中,有一章专门讨论运算符重载。在第11.4.1节中,第一段说明了这一点:
“如果存在赋值运算符X :: operator =(Z),则将类型V的值赋值给类X的对象是合法的,这样V是Z或者存在V到Z的唯一转换。初始化被等同地处理。“
在您的示例中,GCC接受“string s = GenericType();”但拒绝“s = GenericType();”,因此显然不会像初始化那样处理赋值。这是我的第一个线索,在GCC中有些不对劲。
GCC报告了在分配中将GenericType转换为字符串的3个候选者,所有这些都在basic_string.h中。一个是正确的转换,一个报告无效,第三个导致歧义。这是basic_string.h中的运算符重载导致歧义:
/**
* @brief Set value to string of length 1.
* @param c Source character.
*
* Assigning to a character makes this string length 1 and
* (*this)[0] == @a c.
*/
basic_string& operator=(_CharT __c) {
this->assign(1, __c);
return *this;
}
这不是有效的转换,因为它接受的操作数与传递给它的对象类型不匹配。在任何地方都没有作为对尝试的char的赋值,因此这种转换不应该是导致歧义的候选者。 GCC似乎将模板类型与其成员中的操作数类型混合在一起。
答案 2 :(得分:2)
我的问题是:如何在不修改主要内容的情况下解决这种歧义?
创建一个名为string
的自己的类,该类没有含糊不清的operator=
,然后using
std
。
显然这不是一个很好的解决方案,但它有效并且main
不必改变。
我认为你不能以任何其他方式获得你想要的行为。
答案 3 :(得分:0)
此解决方案有效
#include <iostream>
#include <string>
#include <type_traits>
using namespace std;
struct GenericType{
operator string(){
return "Hello World";
}
template <typename T, typename = std::enable_if_t <
std::is_same<T, double>::value ||
std::is_same<T, int>::value>>
operator T(){
return 123.4;
}
};
int main(){
int i = GenericType();
string s = GenericType();
double d = GenericType();
cout << i << s << d << endl;
i = GenericType();
s = GenericType();
d = GenericType();
cout << i << s << d << endl;
}
还有一个更通用的解决方案。我认为您不需要为每种算术类型创建运算符,因为隐式转换可以解决问题。
// ...
template <typename T, typename = std::enable_if_t
<std::is_arithmetic<T>::value && !std::is_same<T, char>::value>>
operator T() const
{
return std::stod("123.4");
}
//...