有没有CRTP没有编译时间检查?

时间:2018-01-10 16:35:39

标签: c++ static-cast class-template static-polymorphism

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

#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

为什么演员表没有错误?如何进行编译时检查可以帮助解决此类错误?

2 个答案:

答案 0 :(得分:8)

在CRTP中解决此问题的常用方法是使基类具有私有构造函数,并在模板中声明类型为朋友:

CRTP<A>

在您的示例中,当您意外地C继承CRTP<A>时,由于C不是C的朋友,因此无法调用其构造函数,并且因为C必须构建其所有基础来构建自己,所以你永远不能构造=default。唯一的缺点是,这并不妨碍汇编本身;要获得编译器错误,您必须尝试实际构造private,或者为其编写用户定义的构造函数。在实践中,这仍然是足够好的,这样你就不必在每个派生中添加保护代码,正如其他解决方案所暗示的那样(恕我直言,这违背了整个目的)。

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

注意:根据我的经验,CRTP的构造函数必须是“用户声明”,这意味着你不能使用trivially_constructible。否则在这种情况下,您可以获得聚合初始化,这不会考虑.。同样,如果你试图保持using System.Reflection; using System.IO; // ... string exePath = Assembly.GetExecutingAssembly().Location; string exeFolder = Path.GetDirectoryName(exePath); string scriptPath = Path.Combine(exeFolder,"MyScript.ps1"); var command = new Command(". " + scriptPath); 特征(这不是一个非常重要的特征),这可能是一个问题,但通常它并不重要。

答案 1 :(得分:2)

  

Q1 为什么演员表没有错误?

当没有明智的事情适用时......

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

  

否则,演员的结果是未定义的。

  

Q2 如何进行编译时检查可以帮助解决此类错误?

我无法找到可以在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 我不建议使用建议的解决方案。我认为考虑到偶然的人为错误会带来太多的开销。