次の例のように、複数の暗黙的な変換関数(非明示的なコンストラクターと変換演算子)を持つ単純なクラステンプレートがあるとします。
template<class T>
class Foo
{
private:
T m_value;
public:
Foo();
Foo(const T& value):
m_value(value)
{
}
operator T() const {
return m_value;
}
bool operator==(const Foo<T>& other) const {
return m_value == other.m_value;
}
};
struct Bar
{
bool m;
bool operator==(const Bar& other) const {
return false;
}
};
int main(int argc, char *argv[])
{
Foo<bool> a (true);
bool b = false;
if(a == b) {
// This is ambiguous
}
Foo<int> c (1);
int d = 2;
if(c == d) {
// This is ambiguous
}
Foo<Bar> e (Bar{true});
Bar f = {false};
if(e == f) {
// This is not ambiguous. Why?
}
}
プリミティブ型(bool
、int
)を含む比較演算子は、予想どおりあいまいです-コンパイラーは、変換演算子を使用して左側のテンプレートクラスインスタンスをプリミティブ型、または変換コンストラクターを使用して、右側のプリミティブ型を予期されるクラステンプレートインスタンスに変換します。
ただし、単純なstruct
を含む最後の比較はあいまいではありません。どうして?どの変換関数を使用しますか?
コンパイラmsvc 15.9.7でテスト済み。
[over.binary]/1によると
したがって、任意の2項演算子_
@
_の場合、_x@y
_はx.operator@(y)
またはoperator@(x,y)
として解釈できます。
このルールによれば、_e == f
_の場合、コンパイラはe.operator==(f)
ではなくf.operator==(e)
としてのみ解釈できます。したがって、あいまいさはありません。 Bar
のメンバーとして定義した_operator==
_は、単にオーバーロード解決の候補ではありません。
_a == b
_および_c == d
_の場合、組み込み候補operator==(int, int)
([over.built]/13を参照)は、メンバーとして定義されている_operator==
_と競合します。 _Foo<T>
_の。
メンバー関数として実装された演算子オーバーロードは、それらが呼び出されるオブジェクトである左側のオペランドの暗黙的な変換を許可しません。
演算子オーバーロードの明示的な呼び出しを書き出して、それが何をするのかを正確に理解することは常に役立ちます。
Foo<Bar> e (Bar{true});
Bar f = {false};
// Pretty explicit: call the member function Foo<Bar>::operator==
if(e.operator ==(f)) { /* ... */ }
これはBar
の比較演算子と混同しないでください。左側の暗黙的な変換が必要になるため、これは不可能です。
次のようにBar
とその比較演算子を定義すると、組み込み型で見られるものと同様のあいまいさをトリガーできます。
struct Bar { bool m; };
// A free function allows conversion, this will be ambiguous:
bool operator==(const Bar&, const Bar&)
{
return false;
}
Scott MeyersのEffective C++ 、項目24でこれがうまく示され、説明されています。