Reputation: 8527
I'm playing around with move semantics on a compiler, which has rvalue references but does not support defaulted move constructors. I would like to generate something like the wrapper class below, which works even if the template parameter is an lvalue reference. However, this straightforward approach does not compile because it tries to initialize an int& from an int.
#define USER_DEFINED 0
template <typename T>
struct Wrapper
{
Wrapper(T t)
: m_t(t)
{
}
Wrapper(const Wrapper&) = delete;
Wrapper& operator=(const Wrapper&) = delete;
#if USER_DEFINED
Wrapper(Wrapper&& w)
: m_t(std::move(w.m_t))
{
}
#else
Wrapper(Wrapper&&) = default;
#endif
private:
T m_t;
};
int main()
{
int i = 0;
Wrapper<int&> w1 = Wrapper<int&>(i);
Wrapper<std::string> w2 = Wrapper<std::string>("text");
}
The obvious solution would be to have two move constructors, one for lvalue references and one for all other types. Something like this for example:
template <typename U = T>
Wrapper(typename std::enable_if<!std::is_lvalue_reference<U>::value, Wrapper>::type&& w)
: m_t(std::move(w.m_t))
{
}
template <typename U = T>
Wrapper(typename std::enable_if<std::is_lvalue_reference<U>::value, Wrapper>::type&& w)
: m_t(w.m_t)
{
}
So is this the way to go? Maybe the expression inside the enable_if<>
should be more generic? Or can I use something different from std::move() and have one single constructor for all types?
Upvotes: 2
Views: 724
Reputation: 25409
Okay, here is a solution that I think will work as you want it but I must admit that I don't fully understand how it does so.
The most important change I've made was to replace std::move
with std::forward<T>
in the move constructor. I have also added a move assignment operator but here is the thing I don't understand: Unless everywhere else, it needs std::move
instead of std::forward
! Finally, I have also added a std::forward
to your constructor that accepts a T
so it doesn't make two copies of its argument. It is actually required that we accept the T
by value and use std::forward
here. Overloading for const T&
and T&&
would fail if T
is a reference because then T&&
would also collapse to an lvalue reference and the overload become ambiguous.
#include <iostream>
#include <utility>
template <typename T>
class Wrapper
{
public:
Wrapper(T t) : m_t {std::forward<T>(t)}
{
}
Wrapper(Wrapper&& w) : m_t {std::forward<T>(w.m_t)}
{
}
Wrapper(const Wrapper&) = delete;
Wrapper&
operator=(Wrapper&& w)
{
if (this != &w)
this->m_t = std::move(w.m_t);
return *this;
}
Wrapper&
operator=(const Wrapper&) = delete;
private:
T m_t;
};
Now, let's take it for a test drive with an instrumented type that allows us see what is going on.
struct A
{
A ()
{
std::cerr << "A was default-constructed" << std::endl;
}
A (const A&)
{
std::cerr << "A was copy-constructed" << std::endl;
}
A (A&&)
{
std::cerr << "A was move-constructed" << std::endl;
}
A&
operator=(const A&)
{
std::cerr << "A was copy-assigned" << std::endl;
return *this;
}
A&
operator=(A&&)
{
std::cerr << "A was move-assigned" << std::endl;
return *this;
}
~A ()
{
std::cerr << "A was destroyed" << std::endl;
}
};
int main()
{
A a {};
Wrapper<A> val1 {a};
Wrapper<A> val2 {std::move(val1)};
val1 = std::move(val2);
Wrapper<A&> ref1 {a};
Wrapper<A&> ref2 {std::move(ref1)};
ref1 = std::move(ref2);
}
Compiled with GCC 4.9.1 (Where the whole exercise is actually rather pointless because it supports all kinds of moves already out of the box.) and all optimizations turned off, the output is as follows (comments added).
A was default-constructed ; automatic variable a in main
A was copy-constructed ; copied as Wrapper<A>'s constructor argument
A was move-constructed ; forwarded in Wrapper<A>'s initializer
A was destroyed ; the moved-away from copy as the constructor returns
A was move-constructed ; forwarded in Wrapper<A>'s move-constructor
A was move-assigned ; move-assignment operator in Wrapper<A>
A was move-assigned ; not sure about this one... (apparently caused by the Wrapper<A&> move-assignment)
A was destroyed ; the sub-object of val2
A was destroyed ; the sub-object of val1
A was destroyed ; the automatic variable a in main
Upvotes: 4