11

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?

djsp
  • 2,174
  • 2
  • 19
  • 40

3 Answers3

4

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).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Ideally, the checks would be carried out even if no method were used. If that's not possible and they have to happen inside member functions, the best I can think of is putting them in a private static `constexpr` function called from every member function, so as not to duplicate them. Not elegant, though. – djsp Apr 17 '16 at 20:10
4

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();
}
Community
  • 1
  • 1
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
1

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.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Well, the point was to add checks which catched errors early on and issued clear diagnostics, so the fact that checks happen only if the method is instantiated is a hindrance, not a benefit (this problem seems unavoidable with static methods; only @Cheers' approach with constructors solves it). Moreover, your way doesn't seem to offer any clear diagnostic; the problem would be buried in loads of compiler errors. – djsp Apr 17 '16 at 20:04