18

次の簡単な例を考えてみましょう

struct C
{
    template <typename T> operator T () {return 0.5;}
    operator int () {return 1;}
    operator bool () {return false;}
};

int main ()
{
    C c;
    double x = c;
    std::cout << x << std::endl;
}

Clangでコンパイルすると、次のエラーが発生します

test.cpp:11:12: error: conversion from 'C' to 'double' is ambiguous
    double x = c;
           ^   ~
test.cpp:4:5: note: candidate function
    operator int () {return 1;}
    ^
test.cpp:5:5: note: candidate function
    operator bool () {return false;}
    ^
test.cpp:3:27: note: candidate function [with T = double]
    template <typename T> operator T () {return 0.5;}
                          ^
1 error generated.

GCC や Intel iclc など、他のコンパイラでも同様のエラーが生成されます。

と を削除するoperator intoperator bool。正常にコンパイルされ、期待どおりに動作します。それらの 1 つだけを削除する場合、つまりテンプレート オペレータを保持して と言うとoperator int、非テンプレート バージョンが常に選択されます。

私の理解では、テンプレートと非テンプレートのオーバーロードされた関数が完全に一致するか、両方が同じ変換シーケンスを必要とするという意味で等しい場合にのみ、非テンプレート バージョンが優先されます。ただし、この場合、コンパイラーはオペレーター・テンプレートを完全に一致していると見なしていないようです。そして、boolintオーバーロードの両方が存在する場合、当然、それらはあいまいであると見なされます。

要約すると、私の質問は、この場合、オペレーター テンプレートが完全に一致していると見なされないのはなぜですか?

4

2 に答える 2

10

これを 2 つの異なる問題に分解してみましょう。

1. コンパイラ エラーが発生するのはなぜですか?

struct C
{
    operator bool () {return false;}
    operator int () {return 1;}
};

intとの両方boolが に暗黙的に変換される可能性があるためdouble、コンパイラはどの関数を使用すべきかを知ることができません。使用できる機能は 2 つありますが、どちらも優先されません。

2. テンプレート化されたバージョンが完全に一致しないのはなぜですか?

struct C
{
    template <typename T> operator T () {return 0.5;}
    operator int () {return 1;}
};

operator int()double を要求するときに呼び出されるのはなぜですか?

非テンプレート関数が呼び出されるのは、非テンプレート関数がオーバーロード解決で優先されるためです。(関数テンプレートのオーバーロード)

編集: 私は間違っていました!Yan Zhou がコメントで述べたように、また私が提供したリンクに記載されているように、テンプレート化された関数の完全な一致は、テンプレート化されていない関数よりも優先されます。

コード (g++ 4.7.2 でコンパイル) をテストしたところ、期待どおりに動作しました。0.5つまり、テンプレート化された関数が使用されました。

EDIT2: clangを試してみましたが、説明した動作を再現できます。gcc では正しく動作するため、これは clang のバグのようです。

于 2013-02-20T18:52:09.120 に答える
7

これは面白い。セクション 13.3.3 の重要な部分を読むには 2 つの方法があります。元の例では間違いなく関数テンプレートを呼び出す必要がありますが、非テンプレートの 1 つが削除されたバージョンはあいまいであると主張される可能性があります。

13.3.3:

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

  • いくつかの引数jに対して、ICS_j( F1) は ICS_j( ) よりも優れた変換シーケンスですF2。そうでない場合は、

  • コンテキストはユーザー定義の変換による初期化であり (8.5、13.3.1.5、および 13.3.1.6 を参照)、戻り値の型からF1宛先の型 (つまり、初期化されるエンティティの型) への標準の変換シーケンスは、の戻り値の型から宛先の型への標準の変換シーケンスよりも優れた変換シーケンスF2、またはそうでない場合は、

  • F1非テンプレート関数でF2あり、関数テンプレートの特殊化です。そうでない場合は、

  • F1およびF2は関数テンプレートの特殊化であり、14.5.6.2 で説明されている半順序規則に従って、関数テンプレートF1は のテンプレートよりも特殊化されています。F2

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

この例では、clang は 3 つの実行可能な候補関数のセットを正しく識別します。

C::operator int()
C::operator bool()
C::operator double<double>()

3 つ目は、関数テンプレートの特殊化です。(上記の構文は正当ではないと思いますが、オーバーロード解決のこの時点では、それはテンプレートとしてではなく、明確な関数型を持つ特殊化として扱われます。)

ここでの引数 (ICS1) の唯一の暗黙的な変換シーケンスは、暗黙的なパラメーターの "lvalue C" と " C&" が完全に一致するため、違いはありません。

この例は、2 番目の箇条書きで説明した状況とまったく同じであるため、関数の戻り値doubleは他の 2 つよりも明らかに優れています。

ここからが奇妙になります: 非常に文字通りの読みでoperator intは、3 番目の箇条書きがあるため、テンプレートの特殊化よりも優れています。「ちょっと待ってください。『より優れている』は反対称であってはなりませんか? がより優れており、かつよりF1優れているとどうして言えますか?」残念ながら、標準はそのようなことを明示的に述べていません。「もしそうでなければ」というフレーズのために、2番目の弾丸が3番目の弾丸よりも優先されませんか? はい、定数とです。しかし、基準は、2 番目の箇条書きを満たすと3 番目の箇条書きが適用されなくなるとは言っていません。F2F2F1F1F2(F1,F2)(F2,F1)

もちろん、operator intより優れているわけではなくoperator bool、逆もまた同様であるため、「他のすべての実行可能な機能よりも優れた機能である実行可能な機能が1つだけ」は依然として存在します。

標準的な欠陥として報告する場合を除いて、私はこの奇妙な読み方を正確に支持しているわけではありません。それを行うと、奇妙な結果が生じます (この例から最適ではないオーバーロードを削除すると、プログラムが整形式からあいまいなものに変わります!)。意図は、3 番目の箇条書きがまったく検討される前に、2 番目の箇条書きが両方の方法で検討されることだと思います。

つまり、オーバーロードの解決によって関数テンプレートを選択する必要があり、これは clang のバグです。

于 2013-02-20T21:26:11.230 に答える