次の短い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 Studio2013はfalse
を出力します。つまり、_(bool)b
_で異なるキャスト演算子を呼び出します。規格による正しい動作はどれですか?
私の理解では、operator bool()
は変換を必要としませんが、operator int()
はint
からbool
への変換を必要とするため、コンパイラーは最初のものを選択する必要があります。 const
はそれで何かをしますか、const-conversionはコンパイラによってより「高価」であると見なされますか?
const
を削除すると、すべてのコンパイラが等しくfalse
を出力として生成します。一方、2つのクラスを組み合わせると(両方の演算子は同じクラスになります)、3つのコンパイラーすべてがtrue
出力を生成します。
標準の状態:
派生クラスの変換関数は、2つの関数が同じ型に変換されない限り、基本クラスの変換関数を非表示にしません。
§12.3[class.conv]
つまり、_operator bool
_は_operator int
_によって隠されていません。
標準の状態:
過負荷の解決中、暗黙のオブジェクト引数は他の引数と区別できません。
§13.3.3.1[over.match.funcs]
この場合の「暗黙のオブジェクト引数」はb
で、タイプは_B2 &
_です。 _operator bool
_には_const B2 &
_が必要であるため、コンパイラーは_operator bool
_を呼び出すためにb
にconstを追加する必要があります。これ(他のすべてが等しい)は、_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 int
_と_operator 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
に直接変換されないという事実に関係なく、オーバーロードセットから選択されます。
変換関数operator int()
は、operator bool() const
を介してclangによって選択されます。これは、b
が定数修飾されていないのに対し、boolの変換演算子は。
簡単な理由は、b
をbool
に変換するときの、過負荷解決の候補関数(暗黙のオブジェクトパラメーターが設定されている)は
_operator bool (B2 const &);
operator int (B2 &);
_
ここで、b
はconst修飾されていないため、2番目の方がより適切に一致します。
両方の関数が同じ資格を共有している場合(両方ともconst
かどうか)、直接変換を提供するため、_operator bool
_が選択されます。
ブール値のostreamインサーター([ostream.inserters.arithmetic]によるstd :: basic_ostream :: operator <<(bool val))が、b
からへの変換の結果の値で呼び出されることに同意する場合bool
その変換を掘り下げることができます。
ブール値へのbのキャスト
_(bool)b
_
に評価します
_static_cast<bool>(b)
_
C++ 11、5.4/4 [expr.cast]によると、_const_cast
_は適用されないため(ここでconstを追加または削除しない)。
この静的変換は、C++ 11、5.2.9/4 [expr.static.cast]ごとに許可されます。ただし、発明された変数tのbool t(b);
が整形式。このようなステートメントは、C++ 11、8.5/15 [dcl.init]に従って直接初期化と呼ばれます。
bool t(b);
最も言及されていない標準段落の条項16は次のように述べています(私の強調):
イニシャライザのセマンティクスは次のとおりです。宛先タイプは初期化されるオブジェクトまたは参照のタイプであり、ソースタイプは初期化式のタイプです。
[...]
[...] ソースタイプが(おそらくcv修飾)の場合クラスタイプ、変換関数が考慮されます。
該当する変換関数が列挙され、過負荷の解決によって最適なものが選択されます。
C++ 11、12.3/5 [class.conv]からわかるように、使用可能な変換関数はoperator int ()
とoperator bool() const
です。
派生クラスの変換関数は、2つの関数が同じ型に変換されない限り、基本クラスの変換関数を非表示にしません。
C++ 11、13.3.1.5/1 [over.match.conv]は次のように述べています。
Sとその基本クラスの変換関数が考慮されます。
ここで、Sは変換元のクラスです。
C++ 11、13.3.1.5/1 [over.match.conv](私の強調):
1[...]「cv1T」が初期化されるオブジェクトの型であり、「cv S」が初期化式の型であり、Sがクラスであると仮定します。タイプの場合、候補関数は次のように選択されます。Sとその基本クラスの変換関数が考慮されます。 S内に隠されておらず、タイプT または標準の変換シーケンスを介してタイプTに変換できるタイプを生成する非明示的な変換関数は候補関数です。
したがって、operator bool () const
は_B2
_内に隠されておらず、bool
を生成するため、適用可能です。
int
は標準の変換シーケンスを介してブール値に変換できる型であるため、最後の標準引用符で強調されている部分は、operator int ()
を使用した変換に関連しています。 int
からbool
への変換はシーケンスではなく、C++ 11、4.12/1 [conv.bool]で許可されている単純な直接変換です。
算術、スコープなしの列挙、ポインター、またはメンバー型へのポインターのprvalueは、bool型のprvalueに変換できます。ゼロ値、ヌルポインター値、またはヌルメンバーポインター値はfalseに変換されます。その他の値はすべてtrueに変換されます。
これは、operator int ()
も適用可能であることを意味します。
適切な変換関数の選択は、過負荷解決(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資格を共有している場合、それらの関数の過負荷解決はもはやトリックを行いません。この場合、変換(シーケンス)ランキングが行われます。
operator bool ()
が選択されるのですか?_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番目の標準変換シーケンス。
2番目の標準変換はoperator bool()
の場合はbool
からbool
(ID変換)ですが、operator int ()
の場合の2番目の標準変換はint
からbool
へのブール変換です。
したがって、operator bool ()
を使用した変換シーケンスは、両方の変換関数が同じconst資格を共有している場合に適しています。
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演算子を明示的に含めることで、問題は解決されます。
以前の回答のいくつかは、すでに多くの情報を提供しています。
私の貢献は、「キャスト操作」は「オーバーロード操作」と同様にコンパイルされることです。操作ごとに一意の識別子を持つ関数を作成し、後で必要な演算子またはキャストに置き換えることをお勧めします。
#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セント。