次のコードが有効かどうかを知りたいです。
元の意図は、特定のメンバーへの呼び出しを、派生クラスのメンバーが存在する場合は派生クラスのメンバーにディスパッチするか、派生クラスにこのメンバーがない場合はデフォルトの動作にフォールバックする基本クラスが好きだということです。もう 1 つの用途は、この基本クラスを単独で使用でき、Derived
テンプレート パラメーターが実装ポリシーになることです。いずれにせよ、次の MWE はclang++
、Intel、icpc
および MSVS で正しくコンパイルおよび実行されます。ただしg++
、質問の最後にエラーメッセージが表示されて失敗します(4.4から4.6まで、私が手にしたバージョン)。
call
ポイント(1)、(2)、(3)をcall_dispatch
(これは私が最初に行ったようなものでした)に変更すると、g++
もう文句はありません。ディスパッチ関数と呼び出し元を同じ名前にするのは良い習慣ではないと思います。私はそれがうまくいくかどうか興味があり、不思議なことにそれを試してみるのに十分でした(このアイデアがどのように思いついたのかわかりません)。これの背後にある私の理論的根拠は、pint (1) でcall
1 つのパラメーターで呼び出されるため、オーバーロードの解決が呼び出し元のゼロ パラメーター 1 と一致しないということです。メンバーを持たないSFINAE
ため、ポイント (2)のものとも一致せず、ポイント (3) のものと一致する必要があります。D2
(1)~(3)に名前を付けたときと同じようにcall_dispatch
。
しかしg++
、私や他のコンパイラには同意しません。それで、それは間違った実装ですか、g++
それともコード自体が無効ですか? エラーメッセージに加えて、本当に混乱しています。とはどこvoid (B<D2>::*)()
から&B<D2>::call
来たのですか?彼が呼び出したメンバ ポインタは、D2
のメンバとして定義されていました。
#include <iostream>
#include <functional>
template <typename Derived>
class B
{
public :
void call ()
{
call<Derived>(0); //----------------------------------------- (1)
}
private :
template <typename D, void (D::*)()> class SFINAE {};
template <typename D>
void call (SFINAE<D, &D::call> *) //---------------------------- (2)
{
static_cast<Derived *>(this)->call();
}
template <typename D>
void call (...) //--------------------------------------------- (3)
{
std::cout << "Call B" << std::endl;
}
};
class D1 : public B<D1>
{
public :
void call ()
{
std::cout << "Call D1" << std::endl;
}
};
class D2 : public B<D2> {};
int main ()
{
D1 d1;
D2 d2;
d1.call();
d2.call();
return 0;
}
エラー:
foo.cpp: In member function ‘void B<Derived>::call() [with Derived = D2]’:
foo.cpp:48:13: instantiated from here
foo.cpp:11:9: error: ‘&B<D2>::call’ is not a valid template argument for type ‘void (D2::*)()’ because it is of type ‘void (B<D2>::*)()’
foo.cpp:11:9: note: standard conversions are not allowed in this context
編集
上記のコードで何がうまくいかないのか、まだ完全には理解していません。しかし、SFINAE クラスを具体的に構築せずに、同じ効果をアーカイブする別の方法があると思います。
#include <iostream>
template <typename Derived>
class B
{
public :
void call ()
{
call_dispatch(&Derived::call);
}
template <typename C>
void call_dispatch (void (C::*) ())
{
static_cast<Derived *>(this)->call();
}
void call_dispatch (void (B<Derived>::*) ())
{
std::cout << "Call B" << std::endl;
}
private :
};
class D1 : public B<D1>
{
public :
void call ()
{
std::cout << "Call D1" << std::endl;
}
};
class D2 : public B<D2> {};
int main ()
{
D1 d1;
D2 d2;
d1.call();
d2.call();
return 0;
}
基本的にD1
とD2
はどちらも から派生してB
いるため、式&Derived::call
は常に解決されます。にD1
解決されると&D1::call
、テンプレート バージョン メンバーが使用されます。ではD2
、独自の を持たないcall
ため、&D2::call
に解決されます。また、 @DavidRodríguez-dribeas の&B::call
おかげで、タイプが に
なったことを指摘したため、テンプレートと非テンプレート メンバーは等しく一致しますが、非テンプレートが優先されます。したがって、デフォルトの呼び出しが使用されます。&D2::call
B::call
この新しいコードに欠陥があるかどうかを確認するのに役立ちますか?