I had some C++03 code that implemented swap
for certain classes, to make std::sort
(and other functions) fast.
Unfortunately for me, std::sort
now seems to use std::move
, which means my code is now much slower than it was in C++03.
I know I can use #if __cplusplus >= 201103L
to conditionally define a move-constructor/move-assignment operator, but I'm wondering if there's a better way that doesn't use preprocessor hacks?
(I'd like to avoid the proprocessor hacks because they would be ugly, since not only do I have to test for compiler versions like _MSC_VER >= 1600
, but also because they wouldn't work well with tools like LZZ that don't recognize C++11 move syntax but force me to preprocess the code.)
It seems the question really is: how can a move constructor and move assignment be implemented with a C++03 compilers?
The simple answer is: they can't! However, the simple answer neglects the possibility of creating something which is perfectly valid C++03 code and which become move constructor and move assignment with a C++11 compiler. This approach will need to use some preprocessor hackery but that bit is only used to create a header defining a few tools used for the actual implementation.
Here is a simple header file which happily compiles without any warnings with clang and gcc with C++11 enabled or disabled:
// file: movetools.hpp
#ifndef INCLUDED_MOVETOOLS
#define INCLUDED_MOVETOOLS
INCLUDED_MOVETOOLS
namespace mt
{
#if __cplusplus < 201103L
template <typename T>
class rvalue_reference {
T* ptr;
public:
rvalue_reference(T& other): ptr(&other) {}
operator T&() const { return *this->ptr; }
};
#else
template <typename T>
using rvalue_reference = T&&;
#endif
template <typename T>
rvalue_reference<T> move(T& obj) {
return static_cast<rvalue_reference<T> >(obj);
}
}
#endif
The basic feature is to define a template mt::rvalue_reference<T>
which behaves somewhat like an rvalue reference in C++03 and actually is an rvalue reference (i.e., a T&&
) for C++11. It won't quite deal with C++03 rvalue references but, at least, allows to have move constructors and move assignments defined without actually needing rvalue references.
Note that mt::move()
is just used to later show how rvalue_reference<T>
can be moved even in C++03! The main point is that rvalue_reference<T>
is either something a C++03 compiler understands or T&&
. For this quite reasonable notation it is necessary that the compiler support alias templates. If that isn't the case, the same trick can be applied but using a suitable nested type of a corresponding class template.
Here is an example use of this header:
#include "movetools.hpp"
#include <iostream>
class foo
{
public:
foo() { std::cout << "foo::foo()\n"; }
foo(foo const&) { std::cout << "foo::foo(const&)\n"; }
foo(mt::rvalue_reference<foo> other) {
std::cout << "foo::foo(&&)\n";
this->swap(other);
}
~foo() { std::cout << "foo::~foo()\n"; }
foo& operator= (foo const& other) {
std::cout << "foo::operator=(foo const&)\n";
foo(other).swap(*this);
return *this;
}
foo& operator= (mt::rvalue_reference<foo> other) {
std::cout << "foo::operator=(foo&&)\n";
this->swap(other);
return *this;
}
void swap(foo&) {
std::cout << "foo::swap(foo&)\n";
}
};
int main()
{
foo f0;
foo f1 = f0;
foo f2 = mt::move(f0);
f1 = f2;
f0 = mt::move(f1);
}
That is, the actual business logic is devoid of any preprocessor hackery. The only need to faff about with the preprocessor is in the header movetools.hpp
which doesn't need to be messed with. That is, I actually think it does not use preprocessor hacks to define the actual move constructor or move assignment although the preprocessor is used somewhere. If you insist that you don't want to use macro hackery, it can be done by directing the compiler to look at different headers but that's an implementation detail of the movetools.hpp
.
Take a look at Boost.Move. It has move emulation for c++03. Maybe it can help, but I didn't take a look at the details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With