異なるコンパイラによって呼び出される異なるキャスト演算子


80

次の短いC ++プログラムについて考えてみます。

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

異なるコンパイラでコンパイルすると、さまざまな結果が得られます。Clang3.4とGCC4.4.7ではtrue、を印刷しますが、Visual Studio 2013はを印刷false(bool)bます。つまり、で異なるキャスト演算子を呼び出します。規格による正しい動作はどれですか?

私の理解でoperator bool()は、変換は必要ありませんoperator int()が、inttobool変換が必要になるため、コンパイラーは最初のものを選択する必要があります。それでconst何かをしますか、const-conversionはコンパイラによってより「高価」であると見なされますか?

を削除するとconst、すべてのコンパイラが等しくfalse出力として生成されます。一方、2つのクラスを組み合わせると(両方の演算子が同じクラスになります)、3つのコンパイラーすべてがtrue出力を生成します。


3
私が理解しているように、B2には2つの演算子があります:B :: operator bool()とB2 :: operator int()。両方の演算子はconst演算子ではなく、constバージョンと非constバージョンには違いがあります。boolのconstバージョンをBに追加し、intのconstバージョンをB2に追加して、変更を確認できます。
タヌキ

1
constここでの鍵です。どちらの演算子も同じようにconstであるかどうかに関係なく、期待どおりに動作し、boolバージョンが「優先」されます。constnessが異なると、常に非constバージョンがすべてのプラットフォームで「勝ち」ます。派生クラスと演算子の一貫性が異なるため、VSは他の2つのコンパイラとは動作が異なるため、バグがあるようです。
buc 2014

異なるデフォルトコンストラクターが異なる動作の原因となる可能性がありますか?
ldgorman 2014

16
EDGも印刷しtrueます。GCC、Clang、EDGがすべて同意し、MSVCが同意しない場合、通常はMSVCが間違っていることを意味します。
Jonathan Wakely 2014

1
注:一般にC ++では、戻り値の型はオーバーロードの解決に関与しませ
Matthieu M.

回答:


51

標準の状態:

派生クラスの変換関数は、2つの関数が同じ型に変換されない限り、基本クラスの変換関数を非表示にしません。

§12.3[class.conv]

つまり、 operator boolはによって隠されていませんoperator int

標準の状態:

過負荷の解決中、暗黙のオブジェクト引数は他の引数と区別できません。

§13.3.3.1[over.match.funcs]

この場合の「暗黙のオブジェクト引数」はb、であり、タイプはB2 &です。 operator boolが必要なconst B2 &ため、コンパイラはconstをbに追加して呼び出す必要がありますoperator bool。これ(他のすべてが等しい)はoperator int、より適切に一致します。

標準では、static_cast(このインスタンスでCスタイルのキャストが実行している)はタイプに変換できると規定されていますT(この場合int)に。

宣言 T t(e);いくつかの発明された一時変数についてはは整形式ですt

§5.2.9[expr.static.cast]

したがって、intは、、boolおよびに変換できます。boolも同様にに変換できます。boolます。

標準の状態:

Sとその基本クラスの変換関数が考慮されます。中に隠されておらずS、型または型に変換できる型を生成する非明示的な変換関数T T標準の変換シーケンスを介しは、候補関数です。

§13.3.1.5[over.match.conv]

だから、過負荷セットの構成要素はoperator intoperator bool。他のすべてが等しい、operator intあれば、より適切に一致します(定数を追加する必要がないため)。したがってoperator int、選択する必要があります。

(おそらく直感に反して)標準は、(上記で確立された)オーバーロードセットに追加された戻り値の型(つまり、これらの演算子が変換する型)を考慮しないことに注意してください。ただし、次のいずれかの引数の変換シーケンスが提供されます。それらは、他の引数の変換シーケンスよりも優れています(これは、一貫性があるため、この場合に当てはまります)。

標準の状態:

これらの定義を前提として、すべての引数iについて、ICSi(F1)がICSi(F2)よりも悪い変換シーケンスではない場合、実行可能な関数F1は別の実行可能な関数F2よりも優れた関数であると定義されます。

  • 一部の引数jの場合、ICSj(F1)はICSj(F2)よりも優れた変換シーケンスです。そうでない場合は、
  • コンテキストはユーザー定義の変換による初期化であり、F1の戻り値の型から宛先の型(つまり、初期化されるエンティティの型)への標準変換シーケンスは、戻り値の型からの標準変換シーケンスよりも優れた変換シーケンスです。 F2の宛先タイプへの変換。

§13.3.3[over.match.best]

この場合、引数は1つだけです(暗黙のthisパラメーター)。変換シーケンスB2 &=> B2 &(呼び出しにはoperator int)よりも優れているB2 &=> const B2 &(呼び出しにoperator bool)、したがってoperator int、それは実際に直接変換されないという事実に関わらず、過負荷セットから選択されますbool


2
N3337を入手できます。これは、C ++ 11の後の最初のドラフトであり、ここにはごくわずかな変更のみが無料で含まれています。標準の適切な部分を見つけることに関しては、通常、PDFのセクションを判断し、物事がどこにあるかを学習し(物事を調べたり、標準を引用したりすることによって)、CTRL + Fで関連するキーワードを検索します。
Robert Allan Hennigan Leahy 2014

1
@ikh現在のCまたはC ++標準ドキュメントはどこにありますか?これに対する最良の質問であり、私が知る限り、CとC ++の両方で利用可能なすべてのドラフトをカバーしています。
Shafik Yaghmour 2014

2
それは私が「どちらの変換シーケンスが優れているかを決定した後にのみ適用される」と言ったことです。それでも、これは答えの重要な部分だと思います。暗黙のオブジェクト引数の変換と「戻り値の型の変換」の間に(理論的には)矛盾があり、過負荷解決のさまざまな手順によって明確に解決されます。[over.match.best] /1.4は、これらのステップの分離と、この場合に最終的な標準変換シーケンスが問題にならない理由を明確に示しています。
dyp 2014

2
私はあなたの提案を組み込むために答えを編集し、「最良の」ものを選択するときにこれらの変換演算子の戻り値の型が考慮されない理由を詳しく説明しました。
Robert Allan Hennigan Leahy 2014

2
@RobertAllanHenniganLeahyそれが質問のポイントだったので、コメントだけでなく、回答のどこかに表示されるべきではありませんか?
バーマー2014

9

ショート

変換関数operator int()はconst修飾されていないoperator bool() constため、clang overによって選択されますがb、boolの変換演算子はです。

簡単な理由は、次のように変換bするときに、(暗黙のオブジェクトパラメータが設定された)過負荷解決の候補関数であるということboolです。

operator bool (B2 const &);
operator int (B2 &);

bはconst修飾されていないため、2番目の方がより適切に一致します。

両方の関数が同じ資格(両方constまたは両方)を共有している場合operator boolは、直接変換を提供するため、が選択されます。

キャスト表記による変換、段階的に分析

我々は、ブールのostream挿入([ostream.inserters.arithmetic]あたりとしては、STD :: basic_ostream ::演算子<<(BOOLヴァル))の変換から結果こと値で呼び出されることに同意した場合bbool、我々はその変換に掘ることができます。

1.キャスト表現

ブール値へのbのキャスト

(bool)b

に評価します

static_cast<bool>(b)

に従って C ++ 11、5.4 / 4 [expr.cast]のでconst_cast(追加またはここでCONST除去しない)は適用されません。

この静的変換は、発明された変数tが適切に形成されている場合、C ++ 11、5.2.9 / 4 [expr.static.cast]に従って許可bool t(b);されます。このようなステートメントは、次のように直接初期化と呼ばれます。、C ++ 11、8.5 / 15 [dcl.init]ます。

2.直接初期化 bool t(b);

最も言及されていない標準段落の条項16は次のように述べています(私の強調):

初期化子のセマンティクスは次のとおりです。宛先タイプは初期化されるオブジェクトまたは参照のタイプであり、ソースタイプは初期化式のタイプです。

[...]

[...]ソースタイプが(おそらくcv修飾された)クラスタイプである場合、変換関数が考慮されます。

該当する変換関数が列挙され、過負荷の解決によって最適なものが選択されます。

2.1どの変換機能が利用できますか?

利用可能な変換関数でありoperator int ()且つoperator bool() constとしてので12.3 / 5 [class.conv]は、C ++ 11、教えてくれる。

派生クラスの変換関数は、2つの関数が同じ型に変換されない限り、基本クラスの変換関数を非表示にしません。

一方、C ++ 11、13.3.1.5/1 [over.match.conv] :状態

Sとその基本クラスの変換関数が考慮されます。

ここで、Sは変換元のクラスです。

2.2どの変換機能が適用できますか?

C ++ 11、13.3.1.5 / 1 [over.match.conv](私の強調):

1 [...]「cv1T」が初期化されるオブジェクトのタイプであり、「cv S」が初期化式のタイプであり、Sがクラスタイプであると仮定すると、候補関数は次のように選択されます。 Sとその基本クラスの関数が考慮されます。S内に隠されておらず、タイプTまたは標準の変換シーケンスを介してタイプTに変換できるタイプを生成する非明示的な変換関数が候補関数です。

したがって、operator bool () const内部に隠されておらずB2、を生成するため、適用可能boolです。

最後に、標準的な引用に重点を置いた部分は、用いた変換に関連しているoperator int ()ので、int標準の変換シーケンスを介してブール値に変換することができるタイプです。からintへの変換boolはシーケンスではなく、C ++ 11、4.12 / 1 [conv.bool]で許可されている単純な直接変換です。

算術、スコープなしの列挙、ポインター、またはメンバー型へのポインターのprvalueは、bool型のprvalueに変換できます。ゼロ値、ヌルポインター値、またはヌルメンバーポインター値はfalseに変換されます。その他の値はすべてtrueに変換されます。

これは、それoperator int ()も適用できることを意味します。

2.3どの変換機能が選択されていますか?

適切な変換関数の選択は、過負荷解決(C ++ 11、13.3.1.5 / 1 [over.match.conv])を介して実行されます。

過負荷解決は、呼び出す変換関数を選択するために使用されます。

クラスメンバー関数のオーバーロード解決に関しては、特別な「癖」が1つあります。それは、暗黙的なオブジェクトパラメータです。

あたりのC ++ 11、13.3.1 [over.match.funcs]

[...]静的および非静的メンバー関数の両方に暗黙のオブジェクトパラメーターがあります[...]

ここで、非静的メンバー関数のこのパラメーターのタイプは、第4節によると次のとおりです。

  • ref-qualifierなしまたは&ref-qualifierありで宣言された関数の「cvXへの左辺値参照」

  • && ref-qualifierで宣言された関数の「cvXへの右辺値参照」

ここで、Xは関数がメンバーであるクラスであり、cvはメンバー関数宣言のcv修飾です。

これは、(C ++ 11、13.3.1.5 / 2 [over.match.conv]による)、変換関数による初期化で、

引数リストには、初期化式である1つの引数があります。[注:この引数は、変換関数の暗黙的なオブジェクトパラメーターと比較されます。—エンドノート]

過負荷解決の候補関数は次のとおりです。

operator bool (B2 const &);
operator int (B2 &);

明らかに、operator int ()変換はタイプの非定数オブジェクトを使用して要求された場合、より良い試合ですB2ので、operator bool ()必要な資格変換。

両方の変換関数が同じconst資格を共有している場合、それらの関数の過負荷解決はもはやトリックを行いません。この場合、変換(シーケンス)ランキングが行われます。

3.operator bool ()両方の変換関数が同じconst資格を共有しているのに、なぜ選択されるのですか?

からB2への変換boolは、ユーザー定義の変換シーケンスです(C ++ 11、13.3.3.1.2 / 1 [over.ics.user])。

ユーザー定義の変換シーケンスは、最初の標準変換シーケンス、ユーザー定義の変換、2番目の標準変換シーケンスで構成されます。

[...]ユーザー定義の変換が変換関数によって指定されている場合、最初の標準変換シーケンスは、ソースタイプを変換関数の暗黙的なオブジェクトパラメーターに変換します。

C ++ 11、13.3.3.2 / 3 [over.ics.rank]

[...]は、より良い変換シーケンスとより良い変換の関係に基づいて、暗黙的な変換シーケンスの半順序を定義します。

[...]ユーザー定義の変換シーケンスU1は、同じユーザー定義の変換関数またはコンストラクターまたは集計の初期化を含み、U1の2番目の標準変換シーケンスがより優れている場合、別のユーザー定義の変換シーケンスU2よりも優れた変換シーケンスです。 U2の2番目の標準変換シーケンス。

第二の規格の変換がある場合でoperator bool()あるboolboolした場合の第二標準変換は、一方(恒等変換)operator int ()であるintboolブール変換です。

したがって、operator bool ()両方の変換関数が同じconst資格を共有している場合は、を使用した変換シーケンスの方が適しています。


ほとんどの場合、結果をどのように処理しても、結果の計算方法に影響がないことは非常に直感的です。どのように我々は計算a/bコードであるかどうか、同じですfloat f = a/b;int f = a/b;。しかし、これは少し意外な場合があります。
デビッドシュワルツ

1

C ++のbool型には、trueとfalseの2つの値があり、対応する値は1と0です。基本クラス(B)のbool演算子を明示的に呼び出すB2クラスにbool演算子を追加すると、固有の混乱を回避できます。 falseとして。これが私の変更したプログラムです。その場合、演算子boolは演算子boolを意味し、決して演算子intではありません。

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
    operator bool() {
        return B::operator bool();
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

あなたの例では、(bool)bがB2のbool演算子を呼び出そうとしていて、B2はbool演算子を継承しており、int演算子は、支配規則により、int演算子が呼び出され、B2で継承されたbool演算子が呼び出されます。ただし、B2クラス自体にbool演算子を明示的に含めることで、問題は解決されます。


8
プログラムを書くのは良い解決策ですが、なぜこの奇妙なことが詳細に発生するのかについては答えられません。
ikh 2014

4
true and false with corresponding values 1 and 0それは単純化しすぎです。true整数1に変換されますが、それは値が「ある」という意味ではありません1。確かに、下にあるtrue可能性があり42ます。
軌道上でのライトネスレース2014年

B2に明示的なbool演算子を含めることで、B2、演算子int、または演算子boolで呼び出されるものの混乱とコンパイラの依存関係を回避できます。
DebasishJana博士2014

コンパイラの依存関係はありません。標準では、0をfalseに変換し、1をtrueに変換する必要があります。
子犬

1
@LightnessRacesinOrbitもっと要点を言えば、おそらく: boolの値が0または1になることはありません。取ることができる値はfalseand truebool整数型に変換されると0と1に変換される)だけです。
James Kanze 2014

0

以前の回答のいくつかは、すでに多くの情報を提供しています。

私の貢献は、「キャスト操作」は「オーバーロード操作」と同様にコンパイルされることです。操作ごとに一意の識別子を持つ関数を作成し、後で必要な演算子またはキャストに置き換えることをお勧めします。

#include <iostream>

class B {
public:
    bool ToBool() const {
        return false;
    }
};

class B2 : public B {
public:
    int ToInt() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << b.ToBool() << std::endl;
}

そして、後で、演算子またはキャストを適用します。

#include <iostream>

class B {
public:
    operator bool() {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

ちょうど私の2セント。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.