web-dev-qa-db-ja.com

オーバーロードされた演算子と暗黙の変換を含むこのC ++式があいまいなのはなぜですか?

_operator bool_は、次の例での_operator<_の使用を中断します。 boolが特定の演算子と同じようにif (a < 0)式に関連する理由、回避策があるかどうかを誰かが説明できますか?

_struct Foo {
    Foo() {}
    Foo(int x) {}

    operator bool() const { return false; }

    friend bool operator<(const Foo& a, const Foo& b) {
        return true;
    }
};

int main() {
    Foo a, b;
    if (a < 0) {
        a = 0;
    }
    return 1;
}
_

コンパイルすると、次のようになります。

_g++ foo.cpp
foo.cpp: In function 'int main()':
foo.cpp:18:11: error: ambiguous overload for 'operator<' (operand types are 'Foo' and 'int')
     if (a < 0) {
           ^
foo.cpp:18:11: note: candidate: operator<(int, int) <built-in>
foo.cpp:8:17: note: candidate: bool operator<(const Foo&, const Foo&)
     friend bool operator<(const Foo& a, const Foo& b)
_
18
jkj yuio

重要なポイントは次のとおりです。

まず、_operator <_の2つの関連するオーバーロードがあります。

  • operator <(const Foo&, const Foo&)。このオーバーロードを使用するには、Foo(int)を使用してリテラル_0_をFooにユーザー定義で変換する必要があります。
  • operator <(int, int)。このオーバーロードを使用するには、ユーザー定義のoperator bool()を使用してFooboolに変換し、続いてpromotionintに変換する必要があります(これは、標準的には、Bo Perssonが指摘しているように、変換とは異なります。

ここでの質問は次のとおりです。あいまいさはどこから発生しますか?確かに、ユーザー定義の変換のみを必要とする最初の呼び出しは、ユーザー定義の変換とそれに続くプロモーションを必要とする2番目の呼び出しよりも賢明ですか?

しかし、そうではありません。標準では、各候補にランクが割り当てられます。ただし、「ユーザー定義のコンバージョンとそれに続くプロモーション」のランクはありません。これは、ユーザー定義の変換のみを使用する場合と同じランクです。簡単に(ただし非公式に)、ランキングシーケンスは次のようになります。

  1. 完全に一致
  2. (のみ)プロモーションが必要
  3. (のみ)暗黙の変換が必要です(floatからintなどのCから継承された「安全でない」変換を含む)
  4. ユーザー定義の変換が必要

(免責事項:前述のように、これは非公式です。複数の引数が含まれる場合は非常に複雑になります。また、参照や履歴書の修飾についても触れませんでした。これは大まかな概要としてのみ意図されています。)

したがって、これは、うまくいけば、呼び出しがあいまいな理由を説明します。次に、これを修正する方法の実際的な部分について説明します。 ほとんどありませんoperator bool()を提供する人は、整数演算または比較を含む式で暗黙的に使用されることを望んでいますか。 C++ 98では、_std::basic_ios<CharT, Traits>::operator void *_から、メンバーへのポインターまたは不完全なプライベートタイプを含む「改善された」より安全なバージョンまで、あいまいな回避策がありました。幸い、C++ 11では、演算子をexplicitとしてマークする、operator bool()を暗黙的に使用した後、整数拡張を防ぐためのより読みやすく一貫した方法が導入されました。これにより、単に「降格」するのではなく、operator <(int, int)オーバーロードが完全に削除されます。

他の人が言及したように、Foo(int)コンストラクターを明示的と​​してマークすることもできます。これには、operator <(const Foo&, const Foo&)オーバーロードを削除するという逆の効果があります。

3番目の解決策は、追加のオーバーロードを提供することです。例:

  • operator <(int, const Foo&)
  • operator <(const Foo&, int)

この例では、explicitを導入しなかった場合でも、完全一致として上記のオーバーロードよりも後者が優先されます。同じことが当てはまります。ために

  • operator <(const Foo&, long long)

これは、プロモーションのみが必要なため、_a < 0_のoperator <(const Foo&, const Foo&)よりも優先されます。

13
Arne Vogel

ここでの問題は、C++にはa < 0式を処理するための2つのオプションがあることです。

  • aboolに変換し、組み込み演算子0を使用して結果を<と比較します(1回の変換)
  • 0Fooに変換し、その結果を定義した<と比較します(1回の変換)

どちらのアプローチもコンパイラと同等であるため、エラーが発生します。

2番目のケースで変換を削除することにより、これを明示的にすることができます。

if (a < Foo(0)) {
    ...
}
24
dasblinkenlight

コンパイラはbool operator <(const Foo &,const Foo &)operator<(bool, int)のどちらかを選択できないため、どちらもこの状況に適合します。

この問題を修正するには、2番目のコンストラクターをexplicitにします。

_struct Foo
{
    Foo() {}
    explicit Foo(int x) {}

    operator bool() const { return false; }

    friend bool operator<(const Foo& a, const Foo& b)
    {
        return true;
    }
};
_

編集:わかりました、ついに質問の本当のポイントがわかりました:) OPは、コンパイラが候補としてoperator<(int, int)を提供する理由を尋ねますが、 "マルチステップ変換は許可されていません"

回答:はい、operator<(int, int)オブジェクトを呼び出すにはaを変換する必要があります_Foo -> bool -> int_。 しかし、C++標準は、実際には「マルチステップ変換は違法である」とは言っていません。

§12.3.4[class.conv]

最大で1つのユーザー定義変換(コンストラクターまたは変換関数)が1つの値に暗黙的に適用されます。

boolからintユーザー定義ではありません変換。したがって、これは合法であり、コンパイラーはoperator<(int, int)を候補として選択する完全な権利を持っています。

4
WindyFields

これはまさにコンパイラがあなたに言うことです。

コンパイラのif (a < 0)を解決するための1つのアプローチは、提供したFoo(int x)コンストラクタを使用して0からオブジェクトを作成することです。

2つ目は、operator bool変換を使用して、それをint(プロモーション)と比較することです。詳細については、 数値プロモーション セクションをご覧ください。

したがって、コンパイラにとってはあいまいであり、どちらに進むかを決定することはできません。

2
Dusteh