Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++14

crtp

The curiously recurring template pattern may be used to implement a sort of static polymorphism. For example:

#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();
}

This behaves as expected and prints 27.

Now, I would like to add checks to the base class to assert that derived classes meet certain requirements. In the example given above, such checks could be:

#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();
}

This doesn't work because, at the point where Base<Derived> is instantiated, Derived has only been forward-declared, i.e., it's incomplete, and nothing of it is yet known beyond the fact that it's a struct.

I've been scratching my head, as I think these checks could prove helpful to users of the base class, but haven't found any way to do this.

Is it possible?, and if so, how?

like image 779
Kalrish Avatar asked Apr 16 '16 23:04

Kalrish


3 Answers

As noted by Kerrek SB you can move the static assertion into the body of a member function.

And when you provide the functionality as non-static member functions, instead of the current static member function, you can let the asserting function body be the body of a constructor.

E.g.

#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();
}
like image 169
Cheers and hth. - Alf Avatar answered Nov 15 '22 15:11

Cheers and hth. - Alf


As a dirty trick, you could move the static assertion into the body of a member function. Since member function definitions effectively appear immediately after the class definition, the type of Derived is complete inside the member function body.

Beware that member functions of class templates are themselves function templates, and thus they are only instantiated if used (or if the class template is instantiated explicitly).

like image 32
Kerrek SB Avatar answered Nov 15 '22 14:11

Kerrek SB


As an alternative approach (the other answers are pretty good indeed), you can use a private method and rely on 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();
}

This way the error arises only if you actually use print.

like image 1
skypjack Avatar answered Nov 15 '22 15:11

skypjack