【问题标题】:Why is this operator= call ambiguous?为什么这个 operator= 调用不明确?
【发布时间】:2016-05-13 19:46:43
【问题描述】:

我正在使用转发构造函数制作一个瘦派生类。 (请耐心等待,我必须使用 GCC 4.7.2,它缺少继承的构造函数)。

在第一次尝试时,我忘记添加 explicit 关键字并出现错误。有人可以准确解释为什么会发生这个特定错误吗?我无法弄清楚事件的顺序。

#include <memory>

template<typename T>
struct shared_ptr : std::shared_ptr<T>
{
  template<typename...Args>
  /*explicit*/ shared_ptr(Args &&... args)
    : std::shared_ptr<T>(std::forward<Args>(args)...)
  {}
};

struct A {};

struct ConvertsToPtr
{
  shared_ptr<A> ptr = shared_ptr<A>(new A());
  operator shared_ptr<A> const &() const { return ptr; }
};

int main()
{
  shared_ptr<A> ptr;
  ptr = ConvertsToPtr(); // error here
  return 0;
}

错误:

test.cpp: In function ‘int main()’:
test.cpp:28:23: error: ambiguous overload for ‘operator=’ in ‘ptr = ConvertsToPtr()’
test.cpp:28:23: note: candidates are:
test.cpp:9:8: note: shared_ptr<A>& shared_ptr<A>::operator=(const shared_ptr<A>&)
test.cpp:9:8: note: shared_ptr<A>& shared_ptr<A>::operator=(shared_ptr<A>&&)

【问题讨论】:

  • 被 GCC 5+ 接受,被 4.9 和 Clang 拒绝。
  • 至于为什么会出现这个错误:我认为这两种可能是ConvertsToPtr的转换函数结果中的const shared_ptr&lt;A&gt;&amp;构造函数,以及@987654326 @构造函数来自一个临时的shared_ptr&lt;A&gt;,它是从Args = {ConvertsToPtr}构造的。但我不确定这些是否同样好。
  • @Alf meh。 my_namespace::shared_ptr 和 std::shared_ptr 是不同的名称。
  • 无法重现 with mingw g++ 5.1 and visual c++ 2015
  • 我的猜测是编译器“认为”作为 ConvertsToPtr struct 的对象不是由转换产生的 const shared_ptr&lt;A&gt; 也是非常量的。当您将 ConvertsToPtr() 静态转换为 const shared_ptr&lt;A&gt;&amp; 时,一切都可以正常编译。

标签: c++ c++11 implicit-conversion explicit


【解决方案1】:

g++ 4.8.4 也是这种情况,如下所示:
g++ -g -pedantic --std=c++11 -o test main.cpp
VS2015的设置都是默认的。

问题在于编译器试图将ConvertsToPtr() 返回的临时对象转换为shared_ptr 对象。当编译器与explicit 关键字一起使用时,则不会使用构造函数进行此转换。但是,在使用gdb 进行检查时,它似乎正在使用shared_ptr&lt;A&gt; const &amp;() 转换函数来匹配适当的类型。然后,此转换返回一个const shared_ptr &amp;,它在调用赋值运算符时没有歧义(这也与 wojciech Frohmberg 的发现相匹配)。

但是,如果explicit 被省略,则返回shared_ptr 的对象。这可以与赋值运算符的右值版本或 const 左值版本匹配。

根据N4296,Table-11,在用conversion constructor 构造之后,我们有一个rvalue of shared_ptr 对象。然而,重载解析找到了两个匹配项,它们都在Exact Match 之下(右值版本是Identity matching,而另一个在Qualification matching 之下)。

我也检查了VS2015,就像 cmets 中所说的那样,它有效。但是使用一些cout 调试可以看到 const lvalue assignment 右值优先于 rvalue const lvalue refrence 版本对应项。

编辑:我对标准进行了更深入的研究并添加了修改。关于结果VS2015 的已删除文本是错误的,因为我没有定义这两个分配。当两个赋值都被声明时,它确实更喜欢右值。

我假设 VS 编译器将 IdentityQualification 在排名中匹配。但是,正如我得出的结论,是 VS 编译器有问题。 g++ 编译器遵循给定的标准。但是,由于 GCC 5.0 可以作为 Visual Studio 工作,编译器错误的可能性很小,所以我很高兴看到其他专家的见解。

编辑:在 13.3.3.2 中,在我写的更好的排名之后,抽签破坏者之一是:

— S1 和 S2 是引用绑定 (8.5.3),两者都没有引用 声明的非静态成员函数的隐式对象参数 没有 ref 限定符,并且 S1 将右值引用绑定到右值 S2 绑定一个左值引用。

附有一个示例,显示给定的右值(不是右值引用)应该匹配const int &amp;&amp; 而不是const int &amp;。因此我想,可以安全地假设它与我们的案例相关,即使我们有 &amp;&amp; 类型而不是 const &amp;&amp; 类型。我想毕竟GCC 4.7,4.8 还是有问题的。

【讨论】:

  • 当然右值绑定到左值常量引用。尝试用0f 调用void f(float const &amp;)
  • 因此在构造ConvertsToPtr 之后,编译器会查找shared_ptr::operator= 并继续进行重载解析。由于转换运算符,接受shared_ptr const &amp; 的候选人成功。如果没有explicit,接受shared_ptr &amp;&amp; 的候选人也会通过基于构造函数的转换成功。两种转换都不是更好,所以调用是模棱两可的。有道理。
  • @AndyJost:最短的转换路径显然更好,因为 C++ 不允许在序列中进行两次用户定义的转换,所以这是一个编译器错误。
  • 我仍然不明白g++ 是如何在构造函数显式时使用() 运算符进行隐式转换的。根据我在标准中阅读的所有内容,只有conversion functionsconversion constructors 起作用。由于conversion constructors 是不言自明的,所以conversion functions 仅属于[explicit] operator type-id 类型。运算符 () 与此请求不匹配。如果有的话,它应该返回一个错误error: conversion from ConvertsToPtr to non-scalar type shared_ptr&lt;A&gt; requested。我需要一位语言律师。
  • @Cheersandhth.-Alf 不能有涉及两个用户定义的转化的转化路径。我们不必假设 GCC 4.x 有像 that 这样的错误来提出合理的解释。如果没有explicit,则可以通过构造函数转换ConvertsToPtr -> shared_ptr&lt;A&gt; &amp;&amp;。到 (13.3.3.2) 时,应该首选该转换(如果可用)。在我看来,GCC 4.x 最有可能没有给出正确的偏好。如果右值引用是一项新功能,那是可以理解的。
猜你喜欢
  • 1970-01-01
  • 2014-11-18
  • 2019-11-18
  • 1970-01-01
  • 1970-01-01
  • 2016-05-18
  • 2020-06-14
  • 1970-01-01
  • 2010-10-17
相关资源
最近更新 更多