9

给定以下程序:

#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 上编译。它遇到了麻烦,因为它想隐式地从 a 转换为 a GenericTypeintchar它也可以返回 a string,因此存在歧义(operator=(char)并且operator=(string)两者都匹配GenericType)。

但是,复制构造函数很好。

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

4

4 回答 4

10

I believe that gcc and clang are correct.

There are two operator= overloads in play:

string& operator=(string const& str); // (1)
string& operator=(char ch);           // (2)

Both of these operator= overloads require a user-defined conversion from your argument of type GenericType. (1) requires the use of the conversion to string. (2) requires the use of the conversion to int, followed by a standard conversion to char.

The important thing is that both overloads require a user-defined conversion. To determine whether one of these conversions is better than the other, we can look to the overload resolution rules, specifically the following rule from C++11 §13.3.3.2/3 (reformatted for clarity):

User-defined conversion sequence U1 is a better conversion sequence than another user-defined conversion sequence U2 if

  1. they contain the same user-defined conversion function or constructor or aggregate initialization and

  2. the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2.

Note that an and joins the two parts of the rule, so both parts must be satisfied. The first part of the rule is not satisfied: the two user-defined conversion sequences use different user-defined conversion functions.

Therefore, neither conversion is better, and the call is ambiguous.

[I don't have a good suggestion on how to fix the problem without changing the definition of main(). Implicit conversions are usually not a good idea; they are sometimes very useful, but more frequently they are likely to cause overload ambiguities or other weird overloading behavior.]

There was a gcc bug report in which this problem was described, and resolved as by design: compiler incorrectly diagnoses ambigous operator overload.

于 2012-05-24T18:29:56.860 回答
2

我的问题是:如何在不修改 main 内容的情况下解决这种歧义?

创建自己的类,该类string没有歧义,operator=然后没有。usingstd

显然这不是一个很好的解决方案,但它可以工作并且main不必改变。

我认为您无法以任何其他方式获得您想要的行为。

于 2012-06-03T12:42:54.487 回答
2

我相信 GCC 是错误的。在 Bjarne Stroustrup 的《The C++ Programming Language》一书中,有一整章专门讨论运算符重载。在第 11.4.1 节中,第一段是这样说的:

“如果存在赋值运算符 X::operator=(Z) 使得 V 为 Z 或存在 V 到 Z 的唯一转换,则将 V 类型的值赋值给 X 类的对象是合法的。初始化被处理等价的。”

在您的示例中,GCC 接受“string s = GenericType();” 然而拒绝“s = GenericType();”,所以它显然没有像初始化一样对待赋值。那是我的第一个线索,在 GCC 中出现了问题。

GCC 在分配中报告了 3 个将 GenericType 转换为字符串的候选对象,都在 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 似乎将模板类型与其成员中的操作数类型混为一谈。

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

于 2012-05-30T21:00:17.477 回答
1

此解决方案有效

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

//...
于 2021-06-19T20:50:21.170 回答