CRTP 没有编译时检查吗?

Has CRTP no compile time check?

我试图使用 奇怪的重复模板模式 来实现静态多态性,当我注意到 static_cast<> 时,它通常会在编译时检查一个类型是否确实是可转换为另一个,在基 class 声明中遗漏了一个拼写错误,允许代码将基 class 向下转换为其兄弟之一:

#include <iostream>

using namespace std;

template< typename T >
struct CRTP
{
    void do_it( )
    {
        static_cast< T& >( *this ).execute( );
    }
};

struct A : CRTP< A >
{
    void execute( )
    {
        cout << "A" << endl;
    }
};

struct B : CRTP< B >
{
    void execute( )
    {
        cout << "B" << endl;

    }
};

struct C : CRTP< A > // it should be CRTP< C >, but typo mistake
{
    void execute( )
    {
        cout << "C" << endl;
    }
};

int main( )
{
    A a;
    a.do_it( );
    B b;
    b.do_it( );
    C c;
    c.do_it( );
    return 0;
}

程序的输出是:

A
B
A

为什么转换工作没有错误?我如何进行编译时检查以帮助解决此类错误?

Q1 Why does the cast work with no errors?

当 none 明智的事情适用时...

来自https://timsong-cpp.github.io/cppwp/n3337/expr.static.cast#2

Otherwise, the result of the cast is undefined.


Q2 How can I have a compile time check that could help from this type of errors?

我找不到可以在 CRTP 中使用的方法。我能想到的最好的方法是在派生的 类.

中添加 static_assert

例如,如果您将 C 更改为:

struct C : CRTP< A > // it should be CRTP< C >, but typo mistake
{
   static_assert(std::is_base_of<CRTP<C>, C>::value, "");
   void execute( )
   {
      cout << "C" << endl;
   }
};

您将在编译时看到错误。

您可以将其简化为

struct C : CRTP< A > // it should be CRTP< C >, but typo mistake
{
   using ThisType = C;
   static_assert(std::is_base_of<CRTP<ThisType>, ThisType>::value, "");
   void execute( )
   {
      cout << "C" << endl;
   }
};

需要在每个派生类型中添加类似的代码。它并不优雅,但它会起作用。

PS 我不建议使用建议的解决方案。我认为考虑到偶尔的人为错误,开销太大了。

在 CRTP 中解决这个问题的通常方法是使基础 class 有一个私有构造函数,并在模板中声明类型为 friend:

template< typename T >
struct CRTP
{
    void do_it( )
    {
        static_cast< T& >( *this ).execute( );
    }
    friend T;
private:
    CRTP() {};
};

在您的示例中,当您不小心让 C 继承自 CRTP<A> 时,由于 C 不是 CRTP<A> 的友元,它无法调用其构造函数,并且由于 C 必须构建其所有基础来构建自身,因此您永远无法构建 C。唯一的缺点是这本身并不能阻止编译;要获得编译器错误,您要么必须尝试实际构造一个 C,要么为它编写一个用户定义的构造函数。实际上,这仍然足够好,这样您就不必像其他解决方案建议的那样在每个派生中添加保护代码(恕我直言,这破坏了整个目的)。

实例:http://coliru.stacked-crooked.com/a/38f50494a12dbb54.

注意:根据我的经验,CRTP 的构造函数必须是 "user declared",这意味着您不能使用 =default。否则在这种情况下,您可以获得聚合初始化,这将不尊重 private。同样,如果您试图保持 trivially_constructible 特征(这不是一个非常重要的特征),这可能是一个问题,但通常这无关紧要。