Unititialized copy via CRTP template passed by value

I am using a CRTP template and trying to determine the operators that apply to its implementations. I discovered the strange behavior of uninitialized objects.

CRTP base class:

template < class C > struct CRTP { using self_t = C; const self_t& self() const { return static_cast<const self_t&>(*this); } self_t& self() { const CRTP& cs = static_cast<const CRTP&>(*this); return const_cast<self_t&>(cs.self()); } void printValue() { cout << "CRTP value : " << self().getValue() << endl; } }; 

Implementation 1:

 struct Impl : public CRTP<Impl> { Impl() = default; Impl(Impl&&) = default; Impl(const Impl&) = default; explicit Impl(int i) : v(i) { } friend void swap(Impl& l, Impl& r) { using std::swap; swap(lv, rv); } Impl& operator=(Impl o) { swap(*this, o); return *this; } int getValue() const { return v; } private: int v; }; 

Implementation 2:

 template < class Arg > struct Neg : public CRTP< Neg<Arg> > { Neg() = default; Neg(Neg&&) = default; Neg(const Neg&) = default; explicit Neg(Arg arg) : a(arg) { } friend void swap(Neg& l, Neg& r) { using std::swap; swap(la, ra); } Neg& operator=(Neg o) { swap(*this, o); return *this; } int getValue() const { return -a.getValue(); } private: Arg a; }; 

Operators ( operator! Work fine, operator~ shows the problem):

 template < class C > Neg<C> operator~(CRTP<C> v) { return Neg<C>(std::move(v.self())); } template < class C > Neg<C> operator-(const CRTP<C>& v) { return Neg<C>(v.self()); } template < class C > Neg<C> operator-(CRTP<C>&& v) { return Neg<C>(std::move(v.self())); } 

Now with a simple basic:

 int main(void) { auto n = -Impl(10); n.printValue(); n = ~Impl(20); n.printValue(); } 

Compiled with gcc, I:

 CRTP value : -10 CRTP value : 0 

Compiled with CLANG I get:

 CRTP value : -10 CRTP value : -1186799704 

Now I have 2 questions:

  • Is this a standard of behavior? This happens even if I delete the default Constructors.
  • How to implement operators in a "passed by value style"? I have n-ary operators that I want to implement using variadic templates, and I cannot list each combination of lvalue and rvalue links.
+4
source share
1 answer

You slice your objects in the operator ~ parameter. You take CRTP<C> by value, which means that everything behind the CRTP CRTP (for example, members v or a ) will be cut. Then you call Undefined Behavior, selecting a sliced ​​object of type CRTP<C> to enter C , which is not the case.

When you pass by value, you need to pass a value of the correct type — a type that is actually an object. Your objects are not of type CRTP<C> for any C , they are of type derived from CRTP<C> (for some C ).

If you want to keep the signature "one at a time", you need to accept something and check for the presence of the correct base class using SFINAE:

 template <class C> typename std::enable_if<std::is_base_of<CRTP<C>, C>::value, Neg<C>>::type operator~ (C v) { return Neg<C>(std::move(v.self())); } 

Or you can use perfect redirects with the same trick.

+5
source

All Articles