Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why not always use std::forward?

Tags:

c++

c++11

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?

like image 579
Lorah Attkins Avatar asked Mar 25 '16 22:03

Lorah Attkins


1 Answers

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.

like image 75
Yam Marcovic Avatar answered Sep 27 '22 16:09

Yam Marcovic