7

The distinction between std::move and std::forward is well known, we use the latter to preserve the value category of a forwarded object and the former to cast to rvalue reference in order to enable move semantics.

In effective modern C++, a guideline exists that states

use std::move on rvalue references, std::forward on universal references.

Yet in the following scenario (and scenarios where we don't want to change value category),

template <class T>
void f(vector<T>&& a)
{
    some_func(std::move(a)); 
}

where a is not a forwarding reference but a simple rvalue reference, wouldn't it be exactly the same to do the following?

template <class T>
void f(vector<T>&& a)
{
    some_func(std::forward<decltype(a)>(a)); 
}

Since this can be easily encapsulated in a macro like this,

#define FWD(arg) std::forward<decltype(arg)>(arg)

isn't it convenient to always use this macro definition like so?

void f(vector<T>&& a)
{
    some_func(FWD(a)); 
}

Aren't the two ways of writing this exactly equivalent?

TriskalJM
  • 2,393
  • 1
  • 19
  • 20
Lorah Attkins
  • 5,331
  • 3
  • 29
  • 63
  • 3
    I have trouble viewing the macro solution as more convenient than writing `move()`... but this strikes me as an opinion-based question. Both do the same thing – Barry Mar 25 '16 at 22:10
  • 3
    but somtimes the argument is a regular reference and you do want to move the vector. keep using forwarding doesn't accomplish that – David Haim Mar 25 '16 at 22:10
  • @Barry I wanted a confirmation on them being equivalent, thnx for that. I'd like to disagree on whether they're opinion based, simply advocating on a programming style does not imply bias, one might have very good reasons to do it (and be able to elaborate on them) – Lorah Attkins Mar 25 '16 at 22:13
  • @DavidHaim Yes, well I mean in cases like the one I give (where only preserving the value category is needed). I edited the question to make that more clear – Lorah Attkins Mar 25 '16 at 22:15
  • 1
    Adding a macro obscures understanding. If you're doing something deliberately strange, like you're doing here, it should be *obvious* even at the expense of verbosity. – tadman Mar 25 '16 at 22:20
  • @tadman This could be a reason, but after writing move aware code for a real project I found verbosity a major deal breaker, really frustrating stuff .. – Lorah Attkins Mar 25 '16 at 22:24
  • 1
    About "... wouldn't it be exactly the same to do this..." I think it is exactly the same; because vector&& a is an rvalue reference, and making an unconditional cast (move) or a conditional cast (forward) to an rvalue reference makes no difference. – Loreto Mar 25 '16 at 23:06
  • @LorahAttkins Verbosity is usually a sign you're going against the grain of the language which is something you can look for in a code review. If you've got all these macros to minify what you're doing you're basically inventing a dialect of C++ that's not conventional and will be harder to understand. – tadman Mar 25 '16 at 23:24

1 Answers1

8

Eh. Debatable. In your particular example it is equivalent. But I wouldn't make it a habit. One reason is because you want a semantic distinction between forwarding and moving. Another reason is because to have a consistent API you'd have to have MOV in addition to FWD, and that really looks bad and doesn't do anything. More importantly, though, is that your code can fail unexpectedly.

Consider the following code:

#include <iostream>

using namespace std;

#define FWD(arg) std::forward<decltype(arg)>(arg)

struct S {
    S() { cout << "S()\n"; }
    S(const S&) { cout << "S(const S&)\n"; }
    S(S&&) { cout << "S(S&&)\n"; }
};

void some_func(S) {}

void f(S&& s)
{
    some_func(FWD(s)); 
}

int main()
{
    f(S{});
}

This prints out

S()
S(S&&)

However, if I just change the FWD line to have another (seemingly optional) pair of parentheses, like this:

void f(S&& s)
{
    some_func(FWD((s))); 
}

Now we get

S()
S(const S&)

And this is because now we're using decltype on an expression, which evaluates to an lvalue reference.

Yam Marcovic
  • 7,953
  • 1
  • 28
  • 38
  • 1
    You make good points. But I disagree on the "seemingly optional" characterization for the parentheses. If one is working with `decltype` should know [this stuff](http://stackoverflow.com/a/17242295/4224575) and this a mistake doable even in `std::forward – Lorah Attkins Mar 25 '16 at 23:43
  • 2
    @Lorah Yeah, but it's a macro - you can't immediately see that it uses `decltype`. – Barry Mar 25 '16 at 23:48
  • 1
    @Barry I guess you wouldn't see what it does if it was a function either, you always have to read the code; I mostly grasp the "lack of transparency" related to macros as "weird side-effects or mutation of the behavior" that are not obvious even when you read the code (like evaluating an expression twice etc...). I'm not above using a macro for reasons like `FWD` i.e. textual replacement (no double evaluation, no incidental data structures, no hidden control flow). Could you please take a look at [this](http://stackoverflow.com/q/36230428/4224575) ? (it's off topic but I could use some help) – Lorah Attkins Mar 26 '16 at 09:30
  • @LorahAttkins Part of the problem here is the use of decltype in the first place. It's not really needed, and it brings its bag of subtleties along with it. – Yam Marcovic Mar 26 '16 at 14:27
  • @LorahAttkins I agree with you. The macro with std::forward you propose truly works (as much as anything before it). What @YamMarcovic says should be already known anyway. You could use `std::forward` for pretty much everything, unless you specifically want an rvalue cast, in which case you do an `std::move`. – KeyC0de Oct 06 '18 at 12:41