19

この例はVC10とgccでコンパイルされているようです(私のバージョンのgccは非常に古いですが)。

編集:R。MartinhoFernandezはgcc4.7でこれを試しましたが、動作は同じです。

struct Base
{
    operator double() const { return 0.0; }
};

struct foo
{
    foo(const char* c) {}
};

struct Something : public Base
{
    void operator[](const foo& f) {}
};

int main()
{
    Something d;
    d["32"];

    return 0;
}

しかし、clangは文句を言います:

test4.cpp:19:6: error: use of overloaded operator '[]' is ambiguous (with operand types 'Something' and 'const char [3]')
    d["32"]
    ~^~~~~
test4.cpp:13:10: note: candidate function
    void operator[](const foo& f) {}
         ^
test4.cpp:19:6: note: built-in candidate operator[](long, const char *)
    d["32"]
     ^
test4.cpp:19:6: note: built-in candidate operator[](long, const restrict char *)
test4.cpp:19:6: note: built-in candidate operator[](long, const volatile char *)
test4.cpp:19:6: note: built-in candidate operator[](long, const volatile restrict char *)

過負荷の解決では、この式を見て2つの可能な関数を検討しています。

  • Something :: operator []の呼び出し(ユーザー定義の変換後)
  • const char *の組み込み演算子を呼び出します( "32" [d]と考えてください)(ユーザー定義の変換と標準のdoubleからlongへの変換後)。

私がd["32"]として書いた場合d.operator[]("32")、オーバーロードの解決はオプション2を見ることすらなく、clangも正常にコンパイルされます。

編集:(質問の明確化)

これは過負荷解決の複雑な領域のようです。そのため、この場合の過負荷解決を詳細に説明し、標準を引用する回答をいただければ幸いです(不明である可能性が高い/高度なルールがある場合) 。

clangが正しければ、2つがあいまいである理由/一方が他方よりも優先されない理由を知ることにも興味があります。答えは、過負荷解決が2つの候補に含まれる暗黙の変換(ユーザー定義と標準の両方の変換)をどのように考慮するか、および一方が他方よりも優れていない理由を説明する必要があるでしょう。

注:演算子double()が演算子bool()に変更された場合、3つすべて(clang、vc、gcc)は、同様のあいまいなエラーでコンパイルを拒否します。

4

3 に答える 3

12

オーバーロードの解像度があいまいな理由は、段階を追って説明することで簡単に理解できるはずです。

§13.5.5 [over.sub]

したがって、添え字式は、存在する場合、およびオーバーロード解決メカニズム (13.3.3) によって演算子が最適一致関数として選択された場合、型のクラス オブジェクトx[y]として解釈されます。x.operator[](y)xTT::operator[](T1)

ここで、まずオーバーロード セットが必要です。§13.3.1これは、メンバー関数と非メンバー関数に従って構築され、含まれています。より詳細な説明については、私のこの回答を参照してください。

§13.3.1 [over.match.funcs]

p2 候補関数のセットには、同じ引数リストに対して解決されるメンバー関数と非メンバー関数の両方を含めることができます。引数とパラメーターのリストがこの異種セット内で比較できるように、メンバー関数は、メンバー関数が呼び出されたオブジェクトを表す、暗黙的なオブジェクト パラメーターと呼ばれる追加のパラメーターを持つと見なされます。[...]

p3 同様に、適切な場合、コンテキストは、操作対象のオブジェクトを示す暗黙のオブジェクト引数を含む引数リストを作成できます。

// abstract overload set (return types omitted since irrelevant)
f1(Something&, foo const&); // linked to Something::operator[](foo const&)
f2(std::ptrdiff_t, char const*); // linked to operator[](std::ptrdiff_t, char const*)
f3(char const*, std::ptrdiff_t); // linked to operator[](char const*, std::ptrdiff_t)

次に、引数リストが作成されます。

// abstract argument list
(Something&, char const[3]) // 'Something&' is the implied object argument

次に、オーバーロード セットのすべてのメンバーに対して引数リストがテストされます。

f1 -> identity match on argument 1, conversion required for argument 2
f2 -> conversion required for argument 1, conversion required for argument 2 (decay)
f3 -> argument 1 incompatible, argument 2 incompatible, discarded

次に、暗黙の変換が必要であることがわかったので、以下を調べます§13.3.3 [over.match.best] p1

次のように定義ICSi(F)します。

  • F静的メンバー関数の場合、[...] ; それ以外は、
  • letは、リスト内の -th 引数を viable function の -th パラメータの型にICSi(F)変換する暗黙の変換シーケンスを示します。13.3.3.1 は暗黙の変換シーケンスを定義し、13.3.3.2 は、ある暗黙の変換シーケンスが別の変換シーケンスよりも優れた変換シーケンスまたは劣った変換シーケンスであるとはどういう意味かを定義しています。iiF

次に、オーバーロード セット ( )のf1およびの暗黙的な変換シーケンスを作成しましょう。f2§13.3.3.1

ICS1(f1): 'Something&' -> 'Someting&', standard conversion sequence
ICS2(f1): 'char const[3]' -> 'foo const&', user-defined conversion sequence
ICS1(f2): 'Something&' -> 'std::ptrdiff_t', user-defined conversion sequence
ICS2(f2): 'char const[3]' -> 'char const*', standard conversion sequence

§13.3.3.2 [over.ics.rank] p2

標準の変換シーケンス (13.3.3.1.1) は、ユーザー定義の変換シーケンスまたは省略記号の変換シーケンスよりも優れた変換シーケンスです。

よりICS1(f1)も優れておりICS1(f2)、よりもICS2(f1)悪いですICS2(f2)
逆に、ICS1(f2)より悪く、ICS1(f1)よりICS2(f2)優れていICS2(f1)ます。

§13.3.3 [over.match.best]

p1 (続き) これらの定義を考えると、実行可能な関数は、すべての引数に対してが よりも悪い変換シーケンスではないF1場合、別の実行可能な関数よりも優れた関数であると定義され、[...]F2 iICSi(F1)ICSi(F2)

p2 他のすべての実行可能な関数よりも優れた関数である実行可能な関数が 1 つだけある場合、それはオーバーロードの解決によって選択された関数です。そうでない場合、呼び出しの形式が正しくありません。

まあ、ファック。:) そのため、Clang はそのコードを拒否する点で正しいです。

于 2012-01-18T20:42:27.743 に答える
4

両方Something::operator[](const foo& f)とビルトインoperator[](long, const char *)がオーバーロード解決の実行可能な候補関数 (13.3.2) であることは間違いないようです。実引数の型はSomethingconst char*であり、暗黙の変換シーケンス (ICF) は次のとおりだと思います。

  • for Something::operator[](const foo& f): (1-1) 恒等変換、および (1-2)foo("32")を通じてfoo::foo(const char*);
  • for operator[](long, const char *): (2-1)long(double(d))スルーSomething::operator double() const(Base から継承)、および (2-2) 恒等変換。

(13.3.3.2) に従ってこれらの ICF をランク付けすると、(1-1) は (2-1) よりも優れた変換であり、(1-2) は (2-2) よりも悪い変換であることがわかります。 . (13.3.3)の定義によれば、

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

したがって、考慮された 2 つの候補関数はどちらも他の関数より優れていないため、呼び出しは不適切な形式です。つまり、Clang は正しいように見え、コードはコンパイルされません。

于 2012-01-18T19:51:43.157 に答える
3

C++ 11 仕様の 13.6 から、clang がここで正しいように見えます。

13.6 組み込み演算子 [over.built]

箇条 5 で定義された組み込み演算子を表す候補演算子関数は、この節で指定されます。これらの候補関数は、13.3.1.2 で説明されているように、演算子のオーバーロード解決プロセスに参加し、他の目的には使用されません。[ 注: 組み込み演算子は非クラス型のオペランドのみを受け取り、演算子オーバーロードの解決は、オペランド式が元々クラスまたは列挙型を持つ場合にのみ発生するため、演算子オーバーロードの解決は、オペランドがクラスまたは列挙型を持つ場合にのみ組み込み演算子に解決できます。演算子に適した非クラス型へのユーザー定義の変換を持つクラス型、または演算子に適した型に変換できる列挙型がオペランドにある場合。また、この節で指定されている候補演算子関数の一部は、組み込み演算子自体よりも寛容であることに注意してください。13.3.1.2 で説明されているように、オーバーロードの解決によって組み込み演算子が選択された後、式は 5 節で指定された組み込み演算子の要件の対象となり、したがって、そこに指定された追加のセマンティック制約が適用されます。組み込みの候補演算子関数と同じ名前とパラメーターの型を持つユーザー作成の候補がある場合、組み込みの演算子関数は非表示になり、候補関数のセットには含まれません。— エンドノート] オーバーロードの解決によって組み込み演算子が選択された後、式は第 5 節で指定された組み込み演算子の要件の対象となり、したがって、そこに指定された追加のセマンティック制約が適用されます。組み込みの候補演算子関数と同じ名前とパラメーターの型を持つユーザー作成の候補がある場合、組み込みの演算子関数は非表示になり、候補関数のセットには含まれません。— エンドノート] オーバーロードの解決によって組み込み演算子が選択された後、式は第 5 節で指定された組み込み演算子の要件の対象となり、したがって、そこに指定された追加のセマンティック制約が適用されます。組み込みの候補演算子関数と同じ名前とパラメーターの型を持つユーザー作成の候補がある場合、組み込みの演算子関数は非表示になり、候補関数のセットには含まれません。— エンドノート]

:

すべての cv 修飾または cv 修飾されていないオブジェクト型 T に対して、次の形式の候補演算子関数が存在します。

T& operator[](T *, std::ptrdiff_t);

T& operator[](std::ptrdiff_t, T *);

編集

どの演算子関数が存在するかを理解すると、標準のセクション 13.3 で説明されているように、これが標準のオーバーロード解決になります。約 10 ページの詳細ですが、要点は、関数呼び出しがあいまいにならないようにする必要があるということです。すべての引数で実行可能なすべての関数と少なくとも同じくらい一致し、少なくとも 1 つの引数で他の関数よりも一致する単一の関数である必要があります。「より良い」とは正確に何を意味するかについては多くの仕様の詳細がありますが、要約すると (この場合) ユーザー定義の変換演算子またはオブジェクト コンストラクターを必要としない一致の方が必要な一致よりも優れています。

したがって、この場合、実行可能な一致が 2 つあります。

void Something::operator[](const foo& f)
operator[](long, const char *)

最初の引数は最初の引数によりよく一致し、2 番目は 2 番目の引数によりよく一致します。したがって、これらの両方よりも優れた機能が他にない限り、あいまいです。

後者の点は可能な回避策です-追加:

void operator[](const char *a) { return (*this)[foo(a)]; }

クラスに何か

于 2012-01-18T18:59:20.907 に答える