12

次の最小限の例を考えてみましょう。

#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 ----+ 
}

この無邪気な a のコピー構築は、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
4

1 に答える 1

5

標準の現在のバージョンでは、コードが正しくありません (標準化後のドラフト n3376 を見ています)。

リスト初期化のルールは次のように指定します。

13.3.1.7 リスト初期化による初期化 [over.match.list]

1 - 非集合クラス タイプのオブジェクトTがリスト初期化される場合 [...]:

  • 実行可能な初期化子リスト コンストラクターが見つからない場合、オーバーロードの解決が再度実行されます。この場合、候補関数はクラスのすべてのコンストラクターでTあり、引数リストは初期化子リストの要素で構成されます。

ただし、オーバーロードの解決がB::Ptr単一のパラメーターを取るコピー コンストラクターに適用される場合const std::shared_ptr<B> &、引数リストは、型lvalue(*a)の単一の要素で構成されるです。オーバーロードの解決は、変換関数を考慮することは許可されていません: AA::operator B::Ptr

13.3.3.1 暗黙の変換シーケンス [over.best.ics]

4 - ただし、候補であるコンストラクターまたはユーザー定義の変換関数の引数を考慮する場合 [...] 13.3.1.7 [...] 初期化子リストに要素が 1 つだけあり、クラス X への変換がある場合または (cv 修飾されている可能性がある) X への参照は、X のコンストラクターの最初のパラメーターとして考慮されます [...]。標準の変換シーケンスと省略記号変換シーケンスのみが考慮されます。

したがって、このコードを拒否するには g++-4.6 が正しいです。g++-4.7.2 は残念ながらそれを受け入れますが、これは正しくありません。

これを記述する正しい方法は、直接初期化 ( B::Ptr(*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にたどることができます。

この答えには少し不安があります。リストの初期化を介してユーザー定義の変換関数を呼び出すことは可能ですか? ここで、誰かが標準の意図を明確にできるかどうかを確認してください。

于 2012-09-27T21:10:21.683 に答える