我想知道以下代码是否有效。
最初的意思是,我喜欢一个基类,如果它存在,则将调用某个成员调用到派生类成员,或者如果派生类没有此成员,则返回默认行为。另一个用途是该基类可以单独使用,Derived
模板参数成为实现策略。无论如何,以下MWE编译并正确运行clang++
,Intel,icpc
和MSVS。但是它在g++
(从4.4到4.6,我手头上的任何版本)失败,并在问题结尾处出现错误消息。
如果我将call
点(1),(2),(3)更改为call_dispatch
(这是我最初做过的事情),g++
不会抱怨了。我不认为让调度函数和调用者具有相同的名称是一个好习惯。我只是好奇它是否会起作用,并且好奇地尝试一下(我不知道这个想法是怎么来的)。我的理由是,在pint(1)中,使用一个参数调用call
,因此重载决策将与其调用者(零参数1)不匹配。它也不会与点{2}处的SFINAE
匹配,因为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
,并且由于
@DavidRodríguez-dribeas,他指出现在&D2::call
的类型为B::call
,因此模板和非模板成员相同,但非模板是首选。因此使用默认调用。
可以帮我看看这个新代码中是否有任何缺陷吗?
答案 0 :(得分:3)
void(B :: *)()和&amp; B :: call来自哪里?
指向成员的指针类型不是获取此类指针的类型,而是定义成员的类型。
struct base { int x; };
struct derived : base {};
int main() {
std::cout << std::is_same< decltype(&derived::x), int (base::*) >::value << std::endl;
}
以上程序打印1
。在您的情况下,当您使用&D::base
时,编译器会将B<D2>::call
视为基本模板的成员,这是表达式的结果:void (B<D2>::*)()
。
答案 1 :(得分:0)
您SFINAE
的实例化期望函数指针模板参数的类型为void (D2::*)()
,而是类型为void (B<D2>::*)()
(因为call
未被D2
覆盖{1}},它使用B<D2>
中定义的那个。
修改强>
template <typename D>
void call (SFINAE<D, &D::call> *)
的实例化不是在这里失败的。那里没有替代错误。 SFINAE<B<D2>, &B<D2>::call>
的实例化发生替换错误,其中没有回退实例化,因此错误。