【问题标题】:CRTP - Checking from the base class that the derived one meets requirementsCRTP - 从基类检查派生的是否满足要求
【发布时间】:2016-04-16 23:56:25
【问题描述】:

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

#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&lt;Derived&gt; 被实例化时,Derived 只是被前向声明的,也就是说,它是不完整的,除了它是一个结构体这一事实之外,它一无所知。

我一直在摸不着头脑,因为我认为这些检查可能对基类的用户有所帮助,但还没有找到任何方法来做到这一点。

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

【问题讨论】:

标签: c++ c++14 crtp


【解决方案1】:

作为noted by 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();
}

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 理想情况下,即使不使用任何方法,也会执行检查。如果这不可能并且它们必须发生在成员函数中,那么我能想到的最好的方法是将它们放在从每个成员函数调用的私有静态 constexpr 函数中,以免重复它们。不过,这并不优雅。
    【解决方案3】:

    作为一种替代方法(其他答案确实很好),您可以使用私有方法并依赖 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时才会出现这种错误。

    【讨论】:

    • 嗯,关键是要添加早期发现错误并发出明确诊断的检查,因此仅当方法被实例化时才会进行检查这一事实是一个障碍,而不是一个好处(这个问题似乎是不可避免的使用静态方法;只有 @Cheers 使用构造函数的方法可以解决它)。此外,您的方式似乎没有提供任何明确的诊断。问题将隐藏在大量编译器错误中。
    猜你喜欢
    • 1970-01-01
    • 2016-04-26
    • 1970-01-01
    • 1970-01-01
    • 2021-10-25
    • 2019-07-08
    • 2019-08-27
    • 2021-09-10
    • 1970-01-01
    相关资源
    最近更新 更多