web-dev-qa-db-ja.com

変換コンストラクターと変換演算子:優先順位

ここでSOに関する変換演算子とコンストラクターに関するいくつかの質問を読むと、それらの間の相互作用について、つまり「あいまいな」呼び出しがある場合について考えることができました。次のコードを考えてみてください。

_class A;

class B { 
      public: 
         B(){} 

         B(const A&) //conversion constructor
         { 
              cout << "called B's conversion constructor" << endl; 
         } 
};

class A { 
      public: 
         operator B() //conversion operator
         { 
              cout << "called A's conversion operator" << endl; 
              return B(); 
         } 
};

int main()
{
    B b = A(); //what should be called here? apparently, A::operator B()
    return 0;
}
_

上記のコードは、「呼び出されたAの変換演算子」を表示します。つまり、コンストラクターではなく変換演算子が呼び出されます。 Aからoperator B()コードを削除またはコメントアウトすると、コンパイラーは代わりにコンストラクターを使用するように切り替えます(コードに他の変更はありません)。

私の質問は:

  1. コンパイラはB b = A();をあいまいな呼び出しとは見なさないため、ここでは何らかのタイプの優先順位が必要です。この優先順位はどこに確立されているのですか? (C++標準からの参照/引用は高く評価されます)
  2. オブジェクト指向の哲学的見地から、これはコードの動作方法ですか? AオブジェクトがBオブジェクト、AまたはBになる方法について誰が知っていますか? C++によると、答えはAです-オブジェクト指向の実践でこれが当てはまることを示唆するものはありますか?個人的にはどちらにしても理にかなっているので、どのように選択されたかを知りたいです。

前もって感謝します

66
GRB

初期化をコピーし、変換シーケンスで変換を実行すると見なされる候補関数は、変換関数と変換コンストラクターです。これらはあなたの場合です

B(const A&)
operator B() 

さて、それがあなたが彼らを宣言する方法です。オーバーロード解決はそれを抽象化し、各候補を呼び出しの引数に対応するパラメーターのリストに変換します。パラメータは

B(const A&)
B(A&)

2つ目は、変換関数がメンバー関数であるためです。 A&は、候補がメンバー関数であるときに生成される、いわゆる暗黙のオブジェクトパラメーターです。現在、引数のタイプはAです。暗黙のオブジェクトパラメータをバインドする場合、非const参照canは右辺値にバインドします。したがって、別のルールでは、パラメーターが参照である2つの実行可能な関数がある場合、fewest const資格を持つ候補が勝つということです。これが、変換関数が優先される理由です。 operator B constメンバー関数。あいまいさがわかります。

オブジェクト指向の哲学的見地から、これはコードの動作方法ですか? AオブジェクトがBオブジェクト、AまたはBになる方法について詳しく知っているのは誰ですか? C++によると、答えはAです。オブジェクト指向の実践でこれが当てはまることを示唆するものはありますか?個人的にはどちらにしても理にかなっているので、どのように選択されたかを知りたいです。

記録のために、変換関数をconstメンバー関数にすると、GCCはコンストラクターを選択します(したがって、GCCはBにもっとビジネスがあると考えているようです?)ペダンティックモードに切り替えます(-pedantic)診断を発生させます。


標準、8.5/14

それ以外の場合(つまり、残りのコピー初期化の場合)、13.3で説明されているように、ソースタイプから宛先タイプに、または(変換関数が使用される場合)その派生クラスに変換できるユーザー定義の変換シーケンスが列挙されます。 1.4、そして最適なものはオーバーロード解決(13.3)によって選択されます。

そして13.3.1.4

オーバーロード解決は、呼び出されるユーザー定義の変換を選択するために使用されます。 「cv1 T」が初期化されるオブジェクトのタイプであり、Tがクラスタイプであるとすると、候補関数は次のように選択されます。

  • Tの変換コンストラクター(12.3.1)は候補関数です。
  • 初期化式の型がクラス型「cv S」の場合、Sとその基底クラスの変換関数が考慮されます。 S内に隠されておらず、cv非修飾バージョンがTと同じ型であるか、その派生クラスである型を生成するものは、候補関数です。 「Xへの参照」を返す変換関数は、タイプXの左辺値を返すため、候補関数を選択するこのプロセスでXを生成すると見なされます。

どちらの場合も、引数リストには初期化子式という1つの引数があります。 [注:この引数は、コンストラクターの最初のパラメーターおよび変換関数の暗黙のオブジェクトパラメーターと比較されます。 ]

そして13.3.3.2/3

  • [...] S1とS2が参照バインディング(8.5.3)であり、参照が参照する型が最上位のcvを除いて同じ型である場合、標準変換順序S1は標準変換順序S2よりも優れた変換順序です。 -qualifiers、およびS2によって初期化された参照が参照するタイプは、S1によって初期化された参照が参照するタイプよりもcv修飾されます。

MSVS2008はコンストラクターの選択について独自の意見を持っているようです。Aの演算子の定数に関係なく、Bのコピーコンストラクターを呼び出します。したがって、標準が正しい動作を指定している場合でも、ここで注意してください。

MSVSは変換演算子の前に適切なコンストラクターを検索するだけだと思いましたが、Bのコンストラクターからconst Wordを削除すると、Aの演算子B()が呼び出されるようになりました。 、次のコードがBのコンストラクターを呼び出すためです。

A a;

B b = a;
3
Xeor