2

と の 2 つの構造があるGeneric_AとしGeneric_Bます。Generic_Bから導出されGeneric_Aます。Generic_B親のメソッドにアクセスしようとするとGeneric_A、次のエラーが生成されるのはなぜですか。

test2.cpp: In function 'int main()':
test2.cpp:26: error: no matching function for call to 'case1(void (Generic_A::*)()' 

gcc バージョン 4.4.6 を使用してコンパイルされたこのコードは、問題を再現します。

#include <stdio.h>

struct Generic_A
{
    void p1() { printf("%s\n", __PRETTY_FUNCTION__); };
};

struct Generic_B : public Generic_A
{
    void p2() { printf("%s\n", __PRETTY_FUNCTION__); };
};

template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) ) {
    printf("%s\n", __PRETTY_FUNCTION__);
}

template <class T>
void case2( void (T::*p)() ) {
    printf("%s\n", __PRETTY_FUNCTION__);
}

main()
{
  //generates error
    case1<Generic_B>(&Generic_B::p1);

  //compiles fine
    case2<Generic_B>(&Generic_B::p1);
}

2 つの関数呼び出しの唯一の明らかな違いcase1()は、テンプレート引数パラメーターがあり、case2()ないことです。どちらも Generic_B の親 (つまり&Generic_B::p1) のメソッドに関数ポインタを渡すことを許可するべきではありませんか?

また、関数ポインターをキャストcase1すると、エラーが解決する場合があります。

case1<Generic_B>( (void (Generic_B::*)()) &Generic_B::p1);

何が起こっている?

4

1 に答える 1

1

これはトリッキーですが、g++ が正しいことがわかります。

まず、式のタイプ&Generic_B::p1は ですvoid (Generic_A::*)()。コンパイラは を使用Generic_B::してその名前のルックアップを修飾し、 のメンバーを見つけGeneric_Aます。式の型は、 qualified-id内で使用される型ではなく、見つかったメンバーの定義に依存します。

しかし、持っていることも合法です

void (Generic_B::*member)() = &Generic_B::p1;

からvoid (Generic_A::*)()への暗黙的な変換があるためvoid (Generic_B::*)()です。

関数テンプレートが関数呼び出しとして使用される場合、コンパイラは次の 3 つの基本的な手順を実行します (または試みます)。

  1. 関数宣言のテンプレート パラメーターを明示的なテンプレート引数に置き換えます。

  2. 少なくとも 1 つのテンプレート パラメーターをまだ含む関数パラメーターごとに、対応する関数引数をその関数パラメーターと比較して、(おそらく) それらのテンプレート パラメーターを推測します。

  3. 推定されたテンプレート パラメーターを関数宣言に置き換えます。

この場合、関数テンプレート宣言があります

template <class T,class... ARGS>
void case1( void (T::*p)(ARGS...) );

テンプレート パラメーターはTARGSで、関数呼び出し式は

case1<Generic_B>(&Generic_B::p1)

ここで、明示的なテンプレート引数はGeneric_Bで、関数引数は&Generic_B::p1です。

したがって、ステップ 1 で、明示的なテンプレート引数を置き換えます。

void case1( void (Generic_B::*p)(ARGS...) );

ステップ 2、パラメーターの型と引数の型を比較します。

パラメータ タイプ (P標準セクション 14.8.2) はvoid (Generic_B::*)(ARGS...)です。引数の型 ( A) はvoid (Generic_A::*)()です。

C++ 標準 (N3485) 14.8.2.1p4:

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

  • Pが参照型である場合、推定A(つまり、参照によって参照される型) は、変換された よりも cv 修飾されている可能性がありますA

  • 変換されたものは、修飾変換 (4.4) を介して推定に変換Aできる別のポインターまたはメンバー型へのポインターにすることができます。A

  • PクラスでP、形式がsimple-template-idの場合、変換Aされた は deduced の派生クラスになることができますA。同様に、がsimple-template-idPの形式のクラスへのポインターである場合、変換された は、 deduced が指す派生クラスへのポインターになることができます。AA

constそのため、型推定では、 /volatileおよび/または派生からベースへの変換を含む特定の暗黙的な変換が可能ですが、メンバーへのポインターの暗黙的な変換は考慮されません。

このcase1例では、型推定は失敗し、関数は一致しません。

ARGS残念ながら、テンプレート パラメーター パックを空のリストに置き換える必要があることを明示的に指定する方法はありません。既に発見したように、メンバー関数変換への必要なポインターを自分で明示的に実行することで、これを機能させることができますが、それ以外の場合は暗黙的な変換として有効です。

于 2013-09-24T20:56:28.637 に答える