【问题标题】:Ambiguous string::operator= call for type with implicit conversion to int and stringAmbiguous string::operator= 调用隐式转换为 int 和 string 的类型
【发布时间】:2012-05-24 18:16:32
【问题描述】:

给定以下程序:

#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 ,因此存在歧义(operator=(char)operator=(string) 都匹配GenericType)。

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

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

【问题讨论】:

  • 隐式转换是一个很好的麻烦来源。重新考虑你是否真的想要这个......
  • 我愿意。在这一点上,出于好奇,我对此最感兴趣。
  • 您也可以在 C++11 中使用explicit operator int() 等。这可以防止错误,就像使用 getType() 函数一样,因为用户必须显式转换。
  • 在您的评论的早期版本中,您声称您只想为赋值和初始化执行此操作,并询问是否可以将转换限制为这两个操作。他们不能。这是转换问题的一部分,它们会在您可能不希望它们发生的情况下发挥作用。您还包括了替代template &lt;typename T&gt; T get();,好吧,如果您想要assignment,请考虑template &lt;typename T&gt; void assignTo( T&amp; ),因为这将使用户语法更友好(编译器将推断类型)
  • 大卫,感谢您的关注,我在考虑您刚刚试图澄清的内容后编辑了我的评论。我觉得您正在投入大量精力来试图驳回一个有效的问题。无论如何,应用程序对您来说有什么关系?我问了一个简洁的问题,我正在寻找一个简洁的答案。

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


【解决方案1】:

我相信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. U1的第二个标准转换序列优于U2的第二个标准转换序列。

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

因此,两种转换都不是更好,而且调用是模棱两可的。

[关于如何在不更改main() 的定义的情况下解决问题,我没有很好的建议。隐式转换通常不是一个好主意;它们有时非常有用,但更常见的是它们可能会导致重载模糊或其他奇怪的重载行为。]

有一个 gcc 错误报告描述了这个问题,并按设计解决了:compiler incorrectly diagnoses ambigous operator overload.

【讨论】:

  • 我想指出,不知何故,Visual Studio 也设法正确处理了这个问题:string val;值 = 65; cout
  • 微软已经让他们的一位开发人员确认了这个缺陷。我个人认为这是一种耻辱,我认为标准对重载解决的定义可能会更好地解决个人解决这种歧义。在涉及用户指定转换的两个调用链的情况下,导致精确结果的一个很容易被认为是更好的一个(超过一个需要在 pod 类型中进一步转换的)。
【解决方案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 中所述,“只有一级用户定义的隐式转换是合法的。”

【讨论】:

  • 我相信它在 GCC 中构造失败的原因不是因为与编译器的内部不一致,而是因为 basic_string 类没有采用 _CharT 的构造函数(只有一个赋值运算符做)。我怀疑如果它这样做了,那么这也会在构造上失败。
  • 那是因为将 char 初始化为字符串是该规则的一个例外。用 char 初始化字符串是无效的,但将 char 分配给字符串是有效的,如同一本书的第 20.3.7 节所述。
  • 被比较的右值是字符串类型,但是这个重载的操作数需要一个字符。要使其成为可接受的候选对象,必须使用字符串的值初始化 char。比如:char c = string(v)。这没有任何意义!虽然您可以将 char 分配给字符串,但不能将字符串分配给 char,这是该候选者所必需的转换。由于这个转换是无效的,所以这个候选也应该是无效的,因此应该没有歧义。
  • 我查了章节。我明白你的意思,但是 GenericType 可以隐式转换为 int,而 int 可以转换为 char(因为 char 可以转换为 int)。澄清一下,我认为这两种转换之间存在歧义: 1) string::operator=(static_cast(static_cast(GenericType()))) (转换在这里明确写出,但它们是隐式发生的在 gcc 中并匹配 string::operator=(char))。 2) string::operator=(static_cast(GenericType()))。其中 string::operator=(char) 和 string::operator=(const string &) 是两个竞争者。
  • 不,在这种情况下仍然只有一个用户定义的转换。请记住,目标类型不是string,而是char,因为那是函数参数的类型。因此,有一个用户定义的转换(GenericTypeint),然后是一个标准转换(intchar)。我在答案中添加了 C++11 中的确切语言,该语言要求调用是模棱两可的。
【解决方案3】:

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

创建您自己的名为 string 的类,该类没有不明确的 operator=,然后不要将 using std 之一。

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

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

【讨论】:

  • 这是准确的,但不如我最终授予赏金的问题有用。谢谢,不过,您确实提供了正确合理的答案。
【解决方案4】:

此解决方案有效

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

//...

【讨论】:

  • 非常感谢您的回答!实际上,这是唯一对我有用的解决方案。我相信,这是由于模板引入了另一层间接性,对吧?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-03
  • 2013-06-12
  • 2022-08-17
  • 1970-01-01
相关资源
最近更新 更多