9

Using this answer, I invented my own method of emulating move-semantics in C++03 based on swap.

First, I detect move-semantics (i.e. availability of C++03):

#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) ||  \
    defined(_MSC_VER) && _MSC_VER >= 1600
#define HAS_MOVE_SEMANTICS 1
#elif defined(__clang)
#if __has_feature(cxx_rvalue_references)
#define HAS_MOVE_SEMANTICS 1
#else
#define HAS_MOVE_SEMANTICS 0
#endif
#else
#define HAS_MOVE_SEMANTICS 0
#endif

Then I conditionally define a macro called move:

#if !HAS_MOVE_SEMANTICS
#include <algorithm>
namespace _cpp11_detail
{
    template<bool B, class T = void> struct enable_if;
    template<class T> struct enable_if<false, T> { };
    template<class T> struct enable_if<true, T> { typedef T type; };
    template<class T>
    inline char (&is_lvalue(
        T &, typename std::allocator<T>::value_type const volatile &))[2];
    inline char (&is_lvalue(...))[1];
    template<bool LValue, class T>
    inline typename enable_if<!LValue, T>::type move(T v)
    { T r; using std::swap; swap(r, v); return r; }
    template<bool LValue, class T>
    inline typename enable_if<LValue, T>::type move(T &v)
    { T r; using std::swap; swap(r, v); return r; }
    template<bool LValue, class T>
    inline typename enable_if<LValue, T>::type const &move(T const &v)
    { return v; }
}
using _cpp11_detail::move;
namespace std { using _cpp11_detail::move; }
// Define this conditionally, if C++11 is not supported
#define move(...) move<  \
    (sizeof((_cpp11_detail::is_lvalue)((__VA_ARGS__), (__VA_ARGS__))) != 1)  \
>(__VA_ARGS__)
#endif

Then I use it like this:

#include <vector>

std::vector<int> test(std::vector<int> v) { return std::move(v); }

int main()
{
    std::vector<int> q(5, 5);
    int x = 5;
    int y = std::move(x);
    std::vector<int> qq = test(std::move(test(std::move(q))));
}

My question is, how safe is this approach in practice? (assuming it compiles fine)

  1. Are there any practical scenarios in which it could fail to work correctly in C++03 but not in C++11?

  2. What about the opposite -- can it work correctly in C++11 but fail in C++03?

(Note: I'm looking for a practical answer, not a language-lawyer answer. I'm aware that defining new members in namespace std is technically undefined, but in practice that won't cause issues on any compiler so I don't find that worth worrying about for the purposes of this question. I'm worried about cases such as accidental dangling references and the like.)

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    I find this macro a bit scary. This won't allow you to have another `move` function somewhere in the code base (at least you have to take care of proper include order). But I guess it's the best you can get. – leemes Dec 23 '13 at 12:02
  • Not very safe considering you seem to have an error in this condition `#if HAS_MOVE_SEMANTICS` ;) – BartoszKP Dec 23 '13 at 12:36
  • 1
    Maybe you could check against http://www.boost.org/doc/libs/1_55_0/doc/html/move/emulation_limitations.html. – kennytm Dec 23 '13 at 12:44
  • I don't know about safety, but does it even eliminate copies? http://coliru.stacked-crooked.com/a/e41f6517dd9834ad – zch Dec 23 '13 at 12:54
  • `__cplusplus < 201103L` looks very suspicious. Surely you want `HAS_MOVE_SEMANTICS` to be `1` if the compiler uses C++11, not if the compiler doesn't use C++11? –  Dec 23 '13 at 14:11
  • @hvd: At the cost of `swap` and a default constructor, yeah. – user541686 Dec 23 '13 at 16:53
  • @KennyTM: None of those apply (as far as I can tell) because I'm not changing the definition of the class here. – user541686 Dec 23 '13 at 18:48

2 Answers2

3

move does not move, but your move does. This means that in C++11 a std::move on an expression that does not assign it anywhere, or the consumer does not modify data. Yours does.

What is worse is that your move blocks rvo/nrvo, just like C++11. In C++11 your return statement from test would be a bad idea, as the return value would be implicitly moveed. In C++03, as nrvo is blocked on arguments, it would be optimal. So the use of the two is different.

Your std::move return value experiences reference lifetime extension, while the return value in C++11 does not. Code will have to be fully tested in both.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Good call on the first one, although arguably it's semantically wrong to just say `std::move(x);` in C++11, even if it works. As for NRVO/RVO, I think think it works when done on a parameter; it has to be a local variable I think? I wouldn't use it if NRVO would take over in both cases but I don't think that's true here. Last point is true but you shouldn't be doing `Foo const &f = std::move(foo)` in in C++11 either right? – user541686 Dec 23 '13 at 16:58
0

The executive summary seems to be, "it's safe as long as you use it the usual way".

user541686
  • 205,094
  • 128
  • 528
  • 886