Ambiguous string :: operator =调用类型,隐式转换为int和string

时间:2012-05-24 18:16:32

标签: c++ string implicit-conversion overload-resolution conversion-operator

鉴于以下计划:

#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转换为intchar,但它也可以返回string,因此存在歧义({ {1}}和operator=(char)都匹配operator=(string))。

然而,复制构造函数很好。

我的问题是:如何在不修改主要内容的情况下解决这种歧义?我需要做些什么才能修改GenericType来处理这种情况?

4 个答案:

答案 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更好的转换序列,如果

     
      
  1. 它们包含相同的用户定义转换函数或构造函数或聚合初始化

  2.   
  3. U1的第二个标准转换序列优于U2的第二个标准转换序列。

  4.   

请注意,连接规则的两个部分,因此必须满足这两个部分。规则的第一部分不满足:两个用户定义的转换序列使用不同的用户定义转换函数。

因此,转换都不是更好,而且调用也不明确。

[我没有很好的建议如何在不改变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似乎将模板类型与其成员中的操作数类型混合在一起。

编辑:我不知道为字符串赋值整数实际上是合法的,因为整数可以转换为char,可以将其赋值给字符串(尽管字符串不能初始化为炭!)。 GenericType定义到int的转换,从而使该成员成为有效的候选者。但是,我仍然认为这不是一个有效的转换,原因是使用这种转换将导致两个用户定义的赋值隐式转换,首先从GenericType到int,然后从int到string。正如同一本书11.4.1所述,“只有一级用户定义的隐式转换是合法的。”

答案 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");
}

//...