C ++ SFINAE与CRTP,G ++编译错误

时间:2012-06-14 22:39:36

标签: c++ sfinae crtp

我想知道以下代码是否有效。

最初的意思是,我喜欢一个基类,如果它存在,则将调用某个成员调用到派生类成员,或者如果派生类没有此成员,则返回默认行为。另一个用途是该基类可以单独使用,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;
}

基本上,由于D1D2都来自B,因此表达式&Derived::call将始终得到解析。在D1中,它解析为&D1::call,然后使用模板版本成员。在D2中,它没有自己的call,因此&D2::call已解析为&B::call,并且由于 @DavidRodríguez-dribeas,他指出现在&D2::call的类型为B::call,因此模板和非模板成员相同,但非模板是首选。因此使用默认调用。

可以帮我看看这个新代码中是否有任何缺陷吗?

2 个答案:

答案 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>的实例化发生替换错误,其中没有回退实例化,因此错误。