为什么这个operator=调用会产生歧义?

10

我正在制作一个具有转发构造函数的继承类。(请容忍我,我必须使用缺乏继承构造函数的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>&&)

关于为什么会出现这个错误:我认为有两种可能性,一种是从ConvertsToPtr的转换函数的结果中使用const shared_ptr<A>&构造函数,另一种是从临时shared_ptr<A>构造的shared_ptr<A>&&构造函数,其参数为Args = {ConvertsToPtr}。但我不确定它们是否同样有效。 - Kerrek SB
@Alf,这两个名称my_namespace::shared_ptr和std::shared_ptr是不同的。 - AndyJost
无法在mingw g++ 5.1和visual c++ 2015中重现。 - Cheers and hth. - Alf
我在想ConvertsToPtr是一个右值是否会改变从shared_ptr<A> const &shared_ptr<A> &&的转换。但我不知道是否存在这样的规则。也许这是GCC 4.x中的一个错误。 - AndyJost
@WojciechFrohmberg 或者在转换函数上移除 const - T.C.
显示剩余3条评论
1个回答

7
这也适用于以下带有g++ 4.8.4的命令:
g++ -g -pedantic --std=c++11 -o test main.cpp
VS2015设置全部默认。
问题在于,编译器试图将由ConvertsToPtr()返回的临时对象转换为shared_ptr对象。当编译器与explicit关键字一起使用时,该构造函数不会发生此转换。但是,在使用进行检查时,它似乎使用shared_ptr <A> const&()转换函数来匹配适当的类型。此转换然后返回一个const shared_ptr &,调用赋值运算符时没有歧义(这也符合wojciech Frohmberg的研究结果)。
但是,如果省略了explicit,则返回shared_ptr对象。这可以与赋值运算符的rvalue版本或const lvalue版本匹配。
根据N4296,表格-11,在使用conversion constructor构造之后,我们拥有rvalue of shared_ptr对象。但是,重载解析找到了两个匹配项,两者都排在Exact Match下面(rvalue版本是Identity matching,而另一个则是Qualification matching)。
我还在VS2015上进行了检查,并像评论中所述一样工作。但是,使用一些cout调试,可以看到const lvalue赋值rvalue优先于rvalue const lvalue引用版本的对应项。 编辑:我深入研究了标准并进行了修改。关于结果VS2015的已删除文本是错误的,因为我没有定义两个赋值。当声明了两个赋值时,确实更喜欢rvalue。
我假设VS编译器将IdentityQualification匹配分别排名。但是,由我的结论是,VS编译器存在故障。g++编译器遵循给定的标准。但是,自GCC 5.0以来可以像Visual Studio一样工作,编译器错误的可能性很小,因此我很乐意看到其他专家的见解。 编辑:在13.3.3.2中,我写了一个抽奖器之一,在我写的更好的排名之后,是:

— S1和S2是引用绑定(8.5.3),它们都不是指向一个没有ref-qualifier声明的非静态成员函数的隐式对象参数的引用,而且S1将rvalue引用绑定到rvalue,S2将lvalue引用绑定。

附带了一个示例,显示给定的rvalue(非rvalue引用)应该匹配const int &&而不是const int &。因此,我猜想,即使我们有&&类型而不是const &&类型,它仍然与我们的情况相关。我想最后GCC4.7,4.8毕竟有缺陷。


1
当然,rvalue 可以绑定到 lvalue 常量引用。尝试使用 0f 调用 void f(float const &). - AndyJost
1
因此,在构建了ConvertsToPtr之后,编译器会查找shared_ptr::operator=并继续进行重载决议。由于转换运算符的存在,接受shared_ptr const&的候选项成功。没有explicit,通过基于构造函数的转换,也可以通过接受shared_ptr &&的候选项成功。没有一个转换更好,所以调用是不明确的。很有道理。 - AndyJost
现在我注意到我完全读错了类型...它确实是一个转换函数而不是()运算符的重载。正在进行编辑... - Aviv
如果在构造函数是显式的情况下进行转换,则返回shared_ptr<A> const &,并且operator=的重载采用const lvalue ref候选项(标识匹配)。我没有看到任何转换(甚至是用户定义的)发生在转换运算符之后。或者我错了吗? - Aviv
1
@Cheersandhth.-Alf 不存在涉及两个用户定义转换的转换路径。我们不必假设GCC 4.x存在像*那样的错误来提出一个合理的解释。没有explicit,通过构造函数允许转换ConvertsToPtr-> shared_ptr<A> &&。根据(13.3.3.2),当可用时应优先选择该转换。我认为最有可能的是GCC 4.x没有给出正确的优先级。如果右值引用是一项新功能,这是可以理解的。 - AndyJost
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接