9

Unless I'm wrong it seems like either works just fine - is there a best practice reason to prefer one over the other?

Example:

struct A
{
    A(){}
    A(const A&){ std::cout << "A(const A&)\n"; }
    A(A&&){ std::cout << "A(A&&)\n"; }
};

struct B
{
    B(){}
    B(const B& right) : x(right.x){ std::cout << "B(const B&)\n"; }
    B(B&& right) : x(std::forward<A>(right.x)){ std::cout << "B(B&&)\n"; }

    A x;
};

struct C
{
    C(){}
    C(const C& right) : x(right.x){ std::cout << "C(const C&)\n"; }
    C(C&& right) : x(std::move(right.x)){ std::cout << "C(C&&)\n"; }

    A x;
};

struct D
{
    D(){}
    D(const D& right) : x(right.x){ std::cout << "D(const D&)\n"; }
    D(D&& right) : x(right.x){ std::cout << "D(D&&)\n"; }

    A x;
};

int main()
{
    std::cout << "--- B Test ---\n";
    B b1;
    B b2(std::move(b1));
    std::cout << "--- C Test ---\n";
    C c1;
    C c2(std::move(c1));
    std::cout << "--- D Test ---\n";
    D d1;
    D d2(std::move(d1));
}

Output:

--- B Test ---
A(A&&)
B(B&&)
--- C Test ---
A(A&&)
C(C&&)
--- D Test ---
A(const A&)
D(D&&)
David
  • 27,652
  • 18
  • 89
  • 138

1 Answers1

14

The question is: Are those really the move constructor / assignment operator for the class? Or do they only look like that from the corner of your eye?

struct X{
  X(X&&); // move ctor #1

  template<class T>
  X(T&&); // perfect forwarding ctor #2

  X& operator=(X&&); // move assignment operator #3

  template<class T>
  X& operator=(T&&); // perfect forwarding ass. operator #4
};

In a real move ctor (#1) and move assignment operator (#3), you will never use std::forward, since, as you correctly assessed, you will always move.

Note that std::forward never makes sense without a perfect forwarding template (T&&). That is exactly the case for #2 and #4. Here, you will never use std::move, since you don't know if you actually got an rvalue (A-OK) or an lvalue (not so much).

See this answer of mine for an explanation of how std::forward actually works.

Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • You're right, I was looking at the wrong ctor. I added some example code to the question. – David Jan 14 '12 at 06:01
  • @Dave: Your use of `std::forward` is wrong, since `A` is not a deduced template parameter. It just so happens to be that it matches the "cast to rvalue" version of `std::forward`. – Xeo Jan 14 '12 at 06:13
  • I may not be not entirely understanding your wording, but doesn't forward (and won't it *always*) resolve to the same thing as std::move in a move ctor (cast to rvalue)? Side note: VS2010 seems to (incorrectly?) accept your operator#2 as the move ctor when #1 isn't present. – David Jan 14 '12 at 06:22
  • @Dave: Yes, MSVC10 incorrectly uses templated constructors as copy / move constructors. On your first point, yes, because `std::forward` is a wrapper for `static_cast`. See what's happening? You always cast to an rvalue because of your incorrect use. In this case it's really absolutely the same as what `std::move` would do (which is also a simple wrapper). – Xeo Jan 14 '12 at 06:31
  • 2
    @Xeo: See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2951.html case E for an example where use of `std::forward` in a *real* move constructor make sense. In this case, use of `std::move` doesn't work. – Howard Hinnant Jan 14 '12 at 14:10
  • @Howard: Oh, thanks! I actually ran into that problem once and resorted to a `(Base&&)` cast. Thinking about it, of course `std::forward` would work, since it's the same cast, but way clearer. Thanks for making the penny drop. ;) – Xeo Jan 15 '12 at 03:42
  • I'm just now remembering more cases I use `std::forward` instead of `std::move` in a move constructor and move assignment: whenever my data members can be references. This happens in `pair` and `unique_ptr`. – Howard Hinnant Jan 15 '12 at 15:29