There is a well-known pattern of NonCopyable mix-in. There is a CRTP version of it. It can be found in many libraries and many examples. But this code seems to behave strange:
#include <type_traits>
struct NonCopyable {
public:
NonCopyable(const NonCopyable &) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
protected:
NonCopyable() = default;
~NonCopyable() = default;
};
template<typename T>
struct NonCopyableCRTP {
public:
NonCopyableCRTP(const NonCopyableCRTP &) = delete;
T& operator=(const T&) = delete;
protected:
NonCopyableCRTP() = default;
~NonCopyableCRTP() = default;
};
struct Test : private NonCopyableCRTP<Test> {};
int main() {
Test t, t2;
static_assert(std::is_copy_assignable_v<Test>); // <--- what?
t2 = t; // <--- this works!
static_assert(!std::is_copy_assignable_v<NonCopyable>);
static_assert(std::is_copy_assignable_v<NonCopyableCRTP<int>>);
return 0;
}
CPPInsights shows what is going on. But I have a question - are all those CRTP examples of NonCopyable around the web broken? And how can we fix them? Should this line:
T& operator=(const T&) = delete;
be replaced with this one:
NonCopyableCRTP<T>& operator=(const NonCopyableCRTP<T>&) = delete;