17

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.)

user541686
  • 205,094
  • 128
  • 528
  • 886
  • Don't define any special members at all and just let them be generated (by using the appropriate building blocks)? – Xeo Jan 22 '14 at 20:33
  • sadly, `std::sort` is not mandated to use `swap`, so how would it help? – TemplateRex Jan 22 '14 at 20:38
  • 2
    @Xeo: Unfortunately Visual C++ doesn't auto-generate move members. – user541686 Jan 22 '14 at 20:48
  • @TemplateRex: I'm not sure, do you have any suggestions on what to do? – user541686 Jan 22 '14 at 20:50
  • I think moving can be implemented in terms of swapping with a temporary. This is just a hack, but you could provide special iterators or wrappers providing this kind of move construction / assignment. (It might be tricky for classes that cannot be efficiently default-constructed.) – dyp Jan 22 '14 at 21:07
  • Don't write code that inhibits move semantics, the one thing I can think of right now is [not to return by const value](http://stackoverflow.com/q/16834937/819272) – TemplateRex Jan 22 '14 at 21:10
  • @Mehrdad it doesn't automatically add them... YET, it will in one of the upcoming updates – Mgetz Jan 22 '14 at 21:15
  • @Mgetz [VS2013 Nov13 CTP](http://blogs.msdn.com/b/vcblog/archive/2013/12/02/c-11-14-core-language-features-in-vs-2013-and-the-nov-2013-ctp.aspx) (which may not be used everywhere because it's only a preview) – dyp Jan 22 '14 at 21:18
  • @dyp I know, hence the *YET* as it will likely be released as part of update 2 or 3. – Mgetz Jan 22 '14 at 21:22
  • @Mgetz no, CTP with compiler updates will only make in the next RTM release – TemplateRex Jan 22 '14 at 21:23
  • @dyp: Whoa, I didn't know there was a CTP for VS2013, thanks for that link! – user541686 Jan 22 '14 at 22:53
  • @dyp: I installed the CTP but I'm not exactly sure what changed... seems like the same compiler as before. – user541686 Jan 23 '14 at 10:30

2 Answers2

10

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.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • @dyp: Not the `mt::move()` is _only_ used in the example to show how the move constructor and move operator is called! The point is: for something which has rvalue references and alias templates (e.g., a C++11 compiler; without alias templates the same can be done using a nested type, though), the functions using `mt::rvalue_reference` as parameters actually take `T&&` as parameter. That is, the goal is _exactly_ addressed by your second comment! – Dietmar Kühl Jan 22 '14 at 23:19
  • I'll retract that comment, was a bit confused. It's actually quite a nice solution. (You don't even have to provide pseudo-move support in C++03 mode, those ctors just have to compile.) – dyp Jan 22 '14 at 23:24
  • The compiler supports `move`: it just does not auto generate move ctors. – Yakk - Adam Nevraumont Jan 23 '14 at 02:31
  • +1 thanks :) not perfect but I don't really expect there's a better way so I'll look into this. – user541686 Jan 23 '14 at 04:12
  • @Yakk: the somewhat C++11 enabled compiler does support move construction and move assignment. However, the code is meant to also compile with a C++03 compiler which doesn't understand rvalue references. Using the approach I described the C++03 compiler can compile the code which turns into move constructor and assignment with a C++11 compiler. – Dietmar Kühl Jan 23 '14 at 20:22
3

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.

Germán Diago
  • 7,473
  • 1
  • 36
  • 59