Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

Emulation limitations
PrevUpHomeNext

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