1

I was thinking about using CRTP classes to help with overloading and wondered what the following bit of code would do:

#include <iostream>
#include <typeinfo>

template <class TDerived>
class FunctionImplementation
{
};

class AbstractFunction
{
};

class BaseFunction : public AbstractFunction, public FunctionImplementation<BaseFunction>
{
};

class DerivedFunction : public BaseFunction, public FunctionImplementation<DerivedFunction>
{
};

template <class TDerived>
void foo(const FunctionImplementation<TDerived>& function) {
    std::cout << "In foo for " << typeid(TDerived).name() << std::endl;
}

int main() {
    BaseFunction base;
    DerivedFunction derived;

    foo(base);
    foo(derived);
}

With GCC 4.2 on OS X it doesn't compile:

overload.cpp: In function ‘int main()’:
overload.cpp:31: error: no matching function for call to ‘foo(DerivedFunction&)’

With Clang 4.0 on the same system, it compiles and does the 'natural' thing when it runs:

In foo for 12BaseFunction
In foo for 15DerivedFunction

With Visual C++ 2010, it also compiles but runs differently:

In foo for class BaseFunction
In foo for class BaseFunction

Finally, GCC 4.7.2 on Linux does not compile but gives a more complete and fairly authoritative-sounding error message:

overload.cpp: In function ‘int main()’:
overload.cpp:31:16: error: no matching function for call to ‘foo(DerivedFunction&)’
overload.cpp:31:16: note: candidate is:
overload.cpp:22:6: note: template<class TDerived> void foo(const FunctionImplementation<TDerived>&)
overload.cpp:22:6: note:   template argument deduction/substitution failed:
overload.cpp:31:16: note:   ‘DerivedFunction’ is an ambiguous base class of ‘const FunctionImplementation<TDerived>’

Which is correct? I am no expert at navigating the language standard...

4

2 に答える 2

3

この場合、gcc が正しいと思います。コンパイラに型推論を実行するように要求していますが、問題は、型の指定された引数が直接でDerivedFunctionはないため、変換を実行する必要があることです。FunctionImplementation<TDerived>この時点で、変換のリストにはFunctionImplementation<BaseFunction>(経由BaseFunction) とFunctionImplementation<DerivedFunction>(直接) が含まれます。これら 2 つの選択肢には順序がないため、コンパイラーはあいまいさで解決します。

標準では、これを §14.8.2.1 [temp.deduct.call]/4,5 で扱います。

(段落 4) 一般に、演繹プロセスは、演繹された A を A と同一にするテンプレート引数値を見つけようとします (型 A が上記のように変換された後)。ただし、違いが認められる 3 つのケースがあります。

[...]

P がクラスであり、P の形式が simple-template-id である場合、変換された A は推定された A の派生クラスである可能性があります。同様に、P が形式 simple-template-id のクラスへのポインターである場合、変換された A は、推定された A が指す派生クラスへのポインターにすることができます。

(パラグラフ 5) これらの選択肢は、そうでなければ型推定が失敗する場合にのみ考慮されます。複数の可能な推定 A が得られる場合、型推定は失敗します。

4 番目の段落では、型推定で引数の型の基数を選択できます。この場合、そのような基数は 2 つあります。5 番目のパラグラフは、前の規則の適用によって複数の結果が得られる場合、型推定は失敗することを決定します。

于 2013-02-20T04:19:19.730 に答える
1

さて、下の答えは間違っています。私はいつも 13.3.1p7 を読んで信じていました

候補が関数テンプレートであるそれぞれの場合において、候補関数テンプレートの特殊化は、テンプレート実引数推定 (14.8.3、14.8.2) を使用して生成されます。これらの候補は、通常の方法で候補関数として処理されます。

そのテンプレート引数推論は、適切なオーバーロード解決機構を使用して、構文的に可能な特殊化 (関数の関数オーバーロード解決など) の中から選択しました。

それは正しくないことが判明しました: テンプレートの引数推定には、完全一致 (pace cv-qualifiers や逆参照など) を主張する独自の非常に制限された一連のルールがあり、派生クラスからテンプレート ベースの引数への変換のみを許可します。ここでは特殊なケースとして見られます。その特殊なケースでは、あいまいさを処理するために関数のオーバーロード解決を使用することを明示的に禁止しています。

したがって、正しい答えについては、上記を参照してください。賛成票が集まっているため、この回答をここに残しておきます。このように間違っているのは私だけではないと思います。

のオーバーロード解決foo(derived)FunctionImplementation<T>、クラス の宣言を検索していDerivedます。クラスDerivedにはそのテンプレートのメンバー スコープの宣言がないため、その基本クラスからの再帰的なルックアップの結果が結合され、階層内の両方の特殊化が生成されます。

Derived
:   Base
    :   AbstractFunction
    ,   FunctionImplementation<Base>
,   FunctionImplementation<Derived>

名前検索を行うときに、基底クラスの派生階層内で宣言が見つかった深さを考慮すると、多重継承を使用するすべての派生クラスの以前の結果に黙って影響を与えることなく、クラスに名前または基底を追加することはできません。代わりに、C++ はユーザーに代わって選択することを拒否し、using-declarations を提供して、どの基本クラスの名前 (この場合は tepmlate) の使用が、参照しようとしている名前であるかを明示的に宣言します。

これの標準は10.2にあり、p3-5は肉です。

于 2013-02-20T05:47:59.323 に答える