CRTP 没有编译时检查吗?

Has CRTP no compile time check?

本文关键字:检查 编译 CRTP      更新时间:2023-10-16

我试图使用奇怪的重复模板模式实现静态多态性,当我注意到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

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

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

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

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

活生生的例子:http://coliru.stacked-crooked.com/a/38f50494a12dbb54。

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

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我不建议使用建议的解决方案。我认为考虑到偶尔的人为错误,开销太大了。