我想知道以下代码是否有效。
最初的意图是,我喜欢一个基类,它将对某个成员的调用分派给派生类成员(如果存在),或者如果派生类没有该成员,则回退到默认行为。另一个用途是这个基类可以自己使用,Derived
模板参数成为实现策略。clang++
无论如何,下面的 MWE 可以在、Intelicpc
和 MSVS上正确编译和运行。但是,它失败了g++
(从 4.4 到 4.6,我使用过的任何版本),并在问题末尾显示错误消息。
如果我将call
点 (1)、(2)、(3) 更改为call_dispatch
(这是我最初所做的那种事情),g++
就不会再抱怨了。我认为调度函数和调用者具有相同的名称不是一个好习惯。我只是好奇它是否会起作用,并且好奇地尝试一下(我不知道这个想法是如何产生的)。我这样做的理由是,在品脱 (1) 处,call
使用一个参数调用,因此重载决议将不匹配其调用者,即零参数一。它也不会匹配SFINAE
点 (2) 处的那个,因为D2
没有成员,然后它应该匹配点 (3) 处的那个。就像 (1)-(3) 被命名的情况一样call_dispatch
。
但g++
不同意我和其他编译器。那么,是不正确的实现g++
还是代码本身无效?除了错误信息真的很混乱。void (B<D2>::*)()
和从何&B<D2>::call
而来?Int 他调用的成员指针被定义为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
解决
了 所以使用默认调用。&B::call
&D2::call
B::call
可以帮我看看这个新代码是否有任何缺陷?