CRTP-从基类中检查派生类是否满足要求

CRTP - Checking from the base class that the derived one meets requirements

本文关键字:是否 满足要求 派生 检查 基类 CRTP-      更新时间:2023-10-16

奇怪的重复模板模式可以用来实现一种静态多态性。例如:

#include <iostream>
template<
 class Derived
>
struct Base
{
    static void print ( )
    {
        std::cout << Derived::number_to_print << 'n';
    }
};
struct Derived final :
    Base<
     Derived
    >
{
    static constexpr unsigned int number_to_print = 27;
};
int main ( )
{
    Derived::print();
}

这将按预期操作并打印27

现在,我想向基类添加检查,以断言派生类满足某些要求。在上面给出的例子中,这样的检查可以是:

#include <iostream>
#include <type_traits>
template<
 class Derived
>
struct Base
{
    // --- Checks begin
    static_assert( std::is_same<
         decltype(Derived::number_to_print),
         unsigned int
        >::value,
        "static member `number_to_print' should be of type `unsigned int'" );
    // --- Checks end
    static void print ( )
    {
        std::cout << Derived::number_to_print << 'n';
    }
};
struct Derived final :
    Base<
     Derived
    >
{
    static constexpr unsigned int number_to_print = 27;
};
int main ( )
{
    Derived::print();
}

这不起作用,因为在实例化Base<Derived>时,Derived只被正向声明,即它是不完整的,除了它是一个结构之外,什么都不知道。

我一直在挠头,因为我认为这些检查可能对基类的用户有帮助,但还没有找到任何方法。

有可能吗?,如果是,怎么做?

正如Kerrek SB所指出的,您可以将静态断言移动到成员函数的主体中。

当您将功能提供为非static成员函数,而不是当前的static成员函数时,您可以让断言函数体成为构造函数的体。

例如

#include <iostream>
#include <type_traits>
using namespace std;
#define STATIC_ASSERT( e ) static_assert( e, #e " // <- is required" )
template< class A, class B >
constexpr auto is_same_() -> bool { return std::is_same<A, B>::value; }
template< class Derived >
struct Base
{
    Base()
    {
        STATIC_ASSERT((
            is_same_< remove_cv_t< decltype( Derived::number_to_print ) >, int >()
            ));
    }
    void print() const
    {
        std::cout << Derived::number_to_print << 'n';
    }
};
struct Derived final
    : Base< Derived >
{
    static constexpr int number_to_print = 27;
};
auto main()
    -> int
{
    Derived::Base().print();
}

作为一个肮脏的技巧,您可以将静态断言移动到成员函数的主体中。由于成员函数定义有效地出现在类定义之后,Derived的类型在成员函数体中是完整的。

注意,类模板的成员函数本身就是函数模板,因此只有在使用时(或者如果类模板是显式实例化的),它们才会被实例化。

作为一种替代方法(其他答案确实很好),您可以使用私有方法并依赖sfinae:

#include <iostream>
#include <type_traits>
template<class Derived>
struct Base {
    static void print ( ) {
        print<Derived>();
    }
private:
    template<class D>
    static
    std::enable_if_t<std::is_same<std::remove_cv_t<decltype(D::N)>, unsigned int>::value>
    print() {
       std::cout << D::N << 'n';
    }
};
struct Derived final: Base<Derived> {
    static constexpr unsigned int N = 27;
};
int main ( ) {
    Derived::print();
}

这样,只有在实际使用print时才会出现错误。