【问题标题】:C++ SFINAE with CRTP, G++ compile error带有 CRTP 的 C++ SFINAE,G++ 编译错误
【发布时间】:2012-06-14 22:39:35
【问题描述】:

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

最初的意图是,我喜欢一个基类,它将对某个成员的调用分派给派生类成员(如果存在),或者如果派生类没有该成员,则回退到默认行为。另一个用途是这个基类可以自己使用,Derived模板参数成为实现策略。无论如何,下面的 MWE 可以在clang++、Intel、icpc 和 MSVS 上正确编译和运行。但是它失败了 g++(从 4.4 到 4.6,我手上的任何版本)并在问题末尾显示错误消息。

如果我将点 (1)、(2)、(3) 处的 call 更改为 call_dispatch(这是我最初所做的那种事情),g++ 就不会再抱怨了。我认为调度函数和调用者具有相同的名称不是一个好习惯。我只是好奇它是否会起作用,并且好奇地尝试一下(我不知道这个想法是如何产生的)。我的理由是,在品脱 (1) 处,call 使用一个参数调用,因此重载决议将不匹配其调用者,即零参数一。它也不会匹配点 (2) 处的 SFINAE 之一,因为 D2 没有该成员,然后它应该匹配点 (3) 处的那个。就像 (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;
}

基本上,因为D1D2都派生自B,所以表达式&amp;Derived::call总是会被解析。在D1 中解析为&amp;D1::call,然后使用模板版本成员。在D2中,没有自己的call,所以&amp;D2::call解析为&amp;B::call,感谢
@DavidRodríguez-dribeas,他指出现在 &amp;D2::call 具有 B::call 类型,因此模板和非模板成员同样匹配,但首选非模板。所以使用默认调用。

能否帮我看看这个新代码是否有任何缺陷?

【问题讨论】:

  • 不是答案,而是解决方法:使用单独的 has_call 特征类可以解决问题。
  • 感谢您的提示。我知道一些修复方法。我仍然想了解这里出了什么问题
  • 没错。实际上,正如提到的,除了 gcc 之外,其他所有人都接受它而不发出警告。我通常使用 clang 开发,因为它往往比 gcc 更严格,并且拒绝 gcc 接受的无效代码。现在我不太确定了
  • Clang++接受代码,我不是100%肯定这两者哪个是正确的...问题是因为D2继承自B&lt;D2&gt;,所以表达式&amp;D::call其实是格式很好,但是奇怪(我认为这是标准的缺陷),类型是void (B&lt;D2&gt;::*)(),而不是void (D2::*)(),所以不能在模板中使用。这是 Substition Failure 还是错误是另一个问题。
  • 你是在 C++11 模式下编译吗?

标签: c++ sfinae crtp


【解决方案1】:

void (B::*)() 和 &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。在您的情况下,当您使用 &amp;D::base 时,编译器会发现 B&lt;D2&gt;::call 作为基本模板的成员,这就是表达式的结果:void (B&lt;D2&gt;::*)()

【讨论】:

    【解决方案2】:

    SFINAE 的实例化期望函数指针模板参数是 void (D2::*)() 类型,但它是 void (B&lt;D2&gt;::*)() 类型(因为 call 没有在 D2 中被覆盖,它使用定义的在B&lt;D2&gt;)。

    编辑

    这里失败的不是template <typename D> void call (SFINAE<D, &D::call> *) 的实例化。那里没有替换错误。替换错误发生在SFINAE&lt;B&lt;D2&gt;, &amp;B&lt;D2&gt;::call&gt; 的实例化中,其中没有备用实例化,因此出现错误。

    【讨论】:

    • 由于类型不匹配,这是一个替换失败,不是吗?因此,根据标准 Is Not An Error 并且您拥有整个 SFINAE,它应该丢弃该特定模板并选择下一个选项,即带有省略号的选项.不应该吗?为什么不呢?
    • 据我所知,引用的部分仅说明了什么是“失败”。正如@DavidRodríguez-dribeas 所说,这不是错误。如果它是唯一的模板成员,那么程序是错误的
    • 撤回。我不得不再考虑一下,但替换失败似乎存在一定程度的间接性。
    猜你喜欢
    • 2011-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多