从隐式转换中复制构造shared_ptr有什么问题?

bit*_*ask 12 c++ compiler-errors g++ type-conversion c++11

考虑这个最小的例子:

#include <memory>

struct B {
  typedef std::shared_ptr<B> Ptr;
};

struct A {
  operator B::Ptr() { // type conversion operator                  <----+
    return std::make_shared<B>();  //                                   |
  }                                //                                   |
};                                 //                                   |
                                   //                                   |
int main() {                       //                                   |
  A* a = new A;                    //                                   |
  B::Ptr{*a}; // copy construction from a's implicit cast to B::Ptr ----+ 
}
Run Code Online (Sandbox Code Playgroud)

的这个无辜的拷贝构造shared_ptr<B> 失败,可怕的G ++ 4.6.3 x86_64的Linux的GNU的,但似乎对工作的g ++ 4.5(请注意,新版本休息,而老的作品!).从我从错误中可以看出来(见下文),g ++ 4.6似乎A通过值而不是(r或l)引用.

那么,问题是,哪个是正确的,哪个是坏的?这种行为应该失败吗?如果是这样,为什么?
据我了解转换规则,此时B::Ptr 应该尝试隐式转换,对吧?


注意:我将此示例简化为技术问题,并且此代码对于任何生产系统都没有意义.

这是精确的错误:

shp.cpp: In function ‘int main()’:
shp.cpp:17:12: error: no matching function for call to ‘std::shared_ptr<B>::shared_ptr(<brace-enclosed initializer list>)’
shp.cpp:17:12: note: candidates are:
/usr/include/c++/4.6/bits/shared_ptr.h:315:2: note: template<class _Alloc, class ... _Args> std::shared_ptr::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...)
/usr/include/c++/4.6/bits/shared_ptr.h:266:17: note: constexpr std::shared_ptr<_Tp>::shared_ptr(std::nullptr_t) [with _Tp = B, std::nullptr_t = std::nullptr_t]
/usr/include/c++/4.6/bits/shared_ptr.h:266:17: note:   no known conversion for argument 1 from ‘A’ to ‘std::nullptr_t’
/usr/include/c++/4.6/bits/shared_ptr.h:258:2: note: template<class _Tp1, class _Del> std::shared_ptr::shared_ptr(std::unique_ptr<_Up, _Ep>&&)
/usr/include/c++/4.6/bits/shared_ptr.h:253:2: note: template<class _Tp1> std::shared_ptr::shared_ptr(std::auto_ptr<_Tp1>&&)
/usr/include/c++/4.6/bits/shared_ptr.h:248:11: note: template<class _Tp1> std::shared_ptr::shared_ptr(const std::weak_ptr<_Tp1>&)
/usr/include/c++/4.6/bits/shared_ptr.h:236:2: note: template<class _Tp1, class> std::shared_ptr::shared_ptr(std::shared_ptr<_Tp1>&&)
/usr/include/c++/4.6/bits/shared_ptr.h:226:7: note: std::shared_ptr<_Tp>::shared_ptr(std::shared_ptr<_Tp>&&) [with _Tp = B, std::shared_ptr<_Tp> = std::shared_ptr<B>]
/usr/include/c++/4.6/bits/shared_ptr.h:226:7: note:   no known conversion for argument 1 from ‘A’ to ‘std::shared_ptr<B>&&’
/usr/include/c++/4.6/bits/shared_ptr.h:218:2: note: template<class _Tp1, class> std::shared_ptr::shared_ptr(const std::shared_ptr<_Tp1>&)
/usr/include/c++/4.6/bits/shared_ptr.h:206:2: note: template<class _Tp1> std::shared_ptr::shared_ptr(const std::shared_ptr<_Tp1>&, _Tp*)
/usr/include/c++/4.6/bits/shared_ptr.h:184:2: note: template<class _Deleter, class _Alloc> std::shared_ptr::shared_ptr(std::nullptr_t, _Deleter, _Alloc)
/usr/include/c++/4.6/bits/shared_ptr.h:165:2: note: template<class _Tp1, class _Deleter, class _Alloc> std::shared_ptr::shared_ptr(_Tp1*, _Deleter, _Alloc)
/usr/include/c++/4.6/bits/shared_ptr.h:146:2: note: template<class _Deleter> std::shared_ptr::shared_ptr(std::nullptr_t, _Deleter)
/usr/include/c++/4.6/bits/shared_ptr.h:129:2: note: template<class _Tp1, class _Deleter> std::shared_ptr::shared_ptr(_Tp1*, _Deleter)
/usr/include/c++/4.6/bits/shared_ptr.h:112:11: note: template<class _Tp1> std::shared_ptr::shared_ptr(_Tp1*)
/usr/include/c++/4.6/bits/shared_ptr.h:103:7: note: std::shared_ptr<_Tp>::shared_ptr(const std::shared_ptr<_Tp>&) [with _Tp = B, std::shared_ptr<_Tp> = std::shared_ptr<B>]
/usr/include/c++/4.6/bits/shared_ptr.h:103:7: note:   no known conversion for argument 1 from ‘A’ to ‘const std::shared_ptr<B>&’
/usr/include/c++/4.6/bits/shared_ptr.h:100:17: note: constexpr std::shared_ptr<_Tp>::shared_ptr() [with _Tp = B]
/usr/include/c++/4.6/bits/shared_ptr.h:100:17: note:   candidate expects 0 arguments, 1 provided
Run Code Online (Sandbox Code Playgroud)

eca*_*mur 5

在当前版本的标准下,代码是不正确的(我正在查看标准后草案n3376).

列表初始化的规则指定:

13.3.1.7按列表初始化初始化[over.match.list]

1 - 当非聚合类类型的对象T被列表初始化时[...]:

  • 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类的所有构造函数,T参数列表由初始化列表的元素组成.

但是,当重载解析应用于B::Ptr获取单个参数的复制构造函数时const std::shared_ptr<B> &,参数列表(*a)lvalue 类型的单个元素组成A; 重载决议不允许考虑转换函数A::operator B::Ptr:

13.3.3.1隐式转换序列[over.best.ics]

4 - 但是,当初始化列表只有一个元素并且转换为某个类X时,在考虑构造函数或用户定义的转换函数的参数时,它是13.3.1.7 [...]的候选者.或者引用(可能是cv-quali fi ed)X被认为是X [...]的构造函数的第一个参数,只考虑标准转换序列和省略号转换序列.

因此g ++ - 4.6拒绝此代码是正确的; 不幸的是,g ++ - 4.7.2接受了它,这是不正确的.

写这个的正确方法是使用direct-initialization(B::Ptr(*a))或a static_cast<B::Ptr>.

对允许转换的限制可以追溯到论文n2672,尽管在该论文中,第13.3.3.1p4段仅适用于用户定义的转换函数的参数.在缺陷978中添加了对构造函数的额外限制:

978.复制初始化的规范不正确

13.3.3.1 [over.best.ics]第4段说,
[...]
这不太正确,因为这适用于构造函数参数,而不仅仅是用户定义的转换函数的参数.

13.3.3.1p4的当前措辞可以追溯到开创性缺陷84,其引入了" 普通法规则,即只调用单个用户定义的转换来进行隐式转换 ".

我对这个答案有点不安; 我问过是否可以通过列表初始化来调用用户定义的转换函数?看看是否有人可以在这里澄清标准的意图.