Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Emulation limitations

Initializing base classes
Template parameters for perfect forwarding
Binding of rvalue references to lvalues
Assignment operator in classes derived from or holding copyable and movable types
Templated assignment operator in copyable and movable types

Like any emulation effort, the library has some limitations users should take in care to achieve portable and efficient code when using the library with C++03 conformant compilers:

When initializing base classes in move constructors, users must cast the reference to a base class reference before moving it or just use BOOST_MOVE_BASE. Example:

Derived(BOOST_RV_REF(Derived) x)             // Move ctor
   : Base(boost::move(static_cast<Base&>(x)))
     //...

or

Derived(BOOST_RV_REF(Derived) x)             // Move ctor
   : Base(BOOST_MOVE_BASE(Base, x))
     //...

If casting is not performed the emulation will not move construct the base class, because no conversion is available from BOOST_RV_REF(Derived) to BOOST_RV_REF(Base). Without the cast or BOOST_MOVE_BASE we might obtain a compilation error (for non-copyable types) or a less-efficient move constructor (for copyable types):

//If Derived is copyable, then Base is copy-constructed.
//If not, a compilation error is issued
Derived(BOOST_RV_REF(Derived) x)             // Move ctor
   : Base(boost::move(x))
     //...

The emulation can't deal with C++0x reference collapsing rules that allow perfect forwarding:

//C++0x
template<class T>
void forward_function(T &&t)
{  inner_function(std::forward<T>(t); }

//Wrong C++03 emulation
template<class T>
void forward_function(BOOST_RV_REF<T> t)
{  inner_function(boost::forward<T>(t); }

In C++03 emulation BOOST_RV_REF doesn't catch any const rlvalues. For more details on forwarding see Constructor Forwarding chapter.

The first rvalue reference proposal allowed the binding of rvalue references to lvalues:

func(Type &&t);
//....

Type t;  //Allowed
func(t)

Later, as explained in Fixing a Safety Problem with Rvalue References this behaviour was considered dangerous and eliminated this binding so that rvalue references adhere to the principle of type-safe overloading: Every function must be type-safe in isolation, without regard to how it has been overloaded

Boost.Move can't emulate this type-safe overloading principle for C++03 compilers:

//Allowed by move emulation
movable m;
BOOST_RV_REF(movable) r = m;

The macro BOOST_COPYABLE_AND_MOVABLE needs to define a copy constructor for copyable_and_movable taking a non-const parameter in C++03 compilers:

//Generated by BOOST_COPYABLE_AND_MOVABLE
copyable_and_movable &operator=(copyable_and_movable&){/**/}

Since the non-const overload of the copy constructor is generated, compiler-generated assignment operators for classes containing copyable_and_movable will get the non-const copy constructor overload, which will surely surprise users:

class holder
{
   copyable_and_movable c;
};

void func(const holder& h)
{
   holder copy_h(h); //<--- ERROR: can't convert 'const holder&' to 'holder&'
   //Compiler-generated copy constructor is non-const:
   // holder& operator(holder &)
   //!!!
}

This limitation forces the user to define a const version of the copy assignment, in all classes holding copyable and movable classes which might be annoying in some cases.

An alternative is to implement a single operator =() for copyable and movable classes using "pass by value" semantics:

T& operator=(T x)    // x is a copy of the source; hard work already done
{
   swap(*this, x);  // trade our resources for x's
   return *this;    // our (old) resources get destroyed with x
}

However, "pass by value" is not optimal for classes (like containers, strings, etc.) that reuse resources (like previously allocated memory) when x is assigned from a lvalue.

Given a movable and copyable class, if a templated assignment operator (*) is added:

class Foo
{
   BOOST_COPYABLE_AND_MOVABLE(Foo)

   public:
   int i;
   explicit Foo(int val)      : i(val)   {}

   Foo(BOOST_RV_REF(Foo) obj) : i(obj.i) {}

   Foo& operator=(BOOST_RV_REF(Foo) rhs)
   {  i = rhs.i; rhs.i = 0; return *this; }

   Foo& operator=(BOOST_COPY_ASSIGN_REF(Foo) rhs)
   {  i = rhs.i; return *this;   } //(1)

   template<class U> //(*) TEMPLATED ASSIGNMENT, potential problem
   Foo& operator=(const U& rhs)
   {  i = -rhs.i; return *this;  } //(2)
};

C++98 and C++11 compilers will behave different when assigning from a [const] Foo lvalue:

Foo foo1(1);
Foo foo2(2);
foo2 = foo1; // Calls (1) in C++11 but (2) in C++98
const Foo foo5(5);
foo2 = foo5; // Calls (1) in C++11 but (2) in C++98

This different behaviour is a side-effect of the move emulation that can't be easily avoided by Boost.Move. One workaround is to SFINAE-out the templated assignment operator with disable_if:

template<class U> // Modified templated assignment
typename boost::disable_if<boost::is_same<U, Foo>, Foo&>::type
   operator=(const U& rhs)
{  i = -rhs.i; return *this;  } //(2)

PrevUpHomeNext