Home > Net >  Ensure derived class implements static method while maintaining default move / move assign
Ensure derived class implements static method while maintaining default move / move assign

Time:04-06

I'd like to ensure some derived classes implement a static method and found this SO question: Ensure derived class implements static method The top answer uses CRTP to solve the issue with a static_assert in the base class destructor to ensure that the template argument type implements a static int foo(int).

However, as noted by a comment on the answer "There is a slight issue with this answer: writing out a destructor has the consequence of disabling the generation of a move constructor and move assignment operator. It also prevents the object from being trivially_destructible. So, while it works, there are downsides to the approach (and such downsides apply to other special members)." – @Matthieu M

Is there a way to avoid this downside? I've tried moving the static_assert to both a member function and static member function of Base, but in both cases it doesn't produce a compile-time error when a derived class doesn't implement static int foo(). Here's the code I've been working with (tweaked from the above SO thread):

#include <type_traits>


template <class T>
class Base {
public:
    //~Base() //This works as expected! Compile error because Bad doesn't implement static int foo()
    //{
    //    static_assert(std::is_same<decltype(T::foo()), int>::value, "ERROR: No 'static int foo()' member provided");
    //}

    static void static_test() //This does not work. No compile time error.
    {
        static_assert(std::is_same<decltype(T::foo()), int>::value, "ERROR: No 'static int foo()' member provided");
    }
};

class Good : public Base<Good> {
public:
    static int foo() { return 42; };
};

class Bad : public Base<Bad> {
public:
    static double foo() { return 42; };
};

int main()
{
    Good g;
    Bad b;
}

CodePudding user response:

What you have is a concept that the class needs to fulfil. You can simply check for it after the definition of the class, probably easiest with a static_assert:

template<typename T>
static constexpr bool assert_is_fooable() {
    static_assert(std::is_same<decltype(T::foo()), int>::value, "ERROR: No 'static int foo()' member provided");
    static_assert(condition_2(), "ERROR: condition 2");
    // More specific error messages given above, this is always
    // true so it can be used in a static_assert
    return true;
}

class Good {
public:
    static int foo() { return 42; };
};

static_assert(assert_is_fooable<Good>());

And it even works if that class is unused anywhere else. You could also put the assert at the place where Good is used that expects it to have static int foo() instead.

This allows the class to be trivially destructible and does not suppress the move constructor or move assignment operator, since it does not actually modify the class.

CodePudding user response:

Not sure if I understand the question correctly, but I think you are thinking too complicated. The workaround to let the compiler generate definition for special members you want to be generated is to declare them default:

struct foo {
    ~foo() {} // prevents the compiler to generate move constructor
    foo(foo&&) = default; // compiler generates a move constructor
}; 
  • Related