0

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;
}

Verwirrt
  • 403
  • 2
  • 13
  • 1
    You can add to the base class: `class_name(c'tor param) = default;` to get the default back for selected constructors – NathanOliver Apr 05 '22 at 12:17

2 Answers2

1

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
}; 
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • That's much simpler than imagined! I'm quite new to move semantics but I'm using a library that provides data structures which require move construction / assignment. Does this also restore the default move assignment operator, and if not is there a similar way to do this too? – Verwirrt Apr 05 '22 at 12:54
  • 1
    @Verwirrt no, I only used the move constructor as an example. In general, if presence of a user defined special member prevents the compiler to generate another spcial member you just need to declare it as `default` to let the compiler generate it. – 463035818_is_not_an_ai Apr 05 '22 at 13:33
1

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.

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • Actually, this will probably work better for my use-case than using CRTP. Simpler to wrap my head around too. Great suggestion. – Verwirrt Apr 05 '22 at 15:58