0

So I was just writing a sample and contrived example of std::forward for my understanding, but it didn't work the way I expected. In the program below

#include <string>
#include <iostream>

struct A
{
    A(const std::string& m) : m_(m) {};
    std::string m_;
};

template<typename T>
void funcB(T&& obj)  // universal reference
{
    std::string local = std::move(obj.m_);   // using std::move on universal reference. Bad.
    std::cout << "funcB : " << local << '\n';
}

template<typename T>
void funcC(T&& obj)  // universal reference
{
    std::string local = std::forward<std::string>(obj.m_);   // using std::move on universal reference
    std::cout << "funcC : " << local << '\n';
}

template<typename T>
void funcD(T&& obj)  // universal reference
{
    T local = std::forward<T>(obj);   // using std::move on universal reference
    std::cout << "funcD : " << local.m_ << '\n';
}

int main()
{
    A obj("firstString");

   //funcA(obj);  // We get compiler error. Rvalue reference cannot be bound to Lvalue
    funcB(obj);  
    std::cout << "Main : " <<  obj.m_ << '\n';

    A obj2("secondString");
    funcC(obj2);
    std::cout << "Main : " << obj2.m_ << '\n';

    A obj3("thirdString");
    funcD(obj3);
    std::cout << "Main : " << obj3.m_ << '\n';
}

In the output

funcB : firstString
Main : 
funcC : secondString
Main : 
funcD : thirdString
Main : thirdString

In funcC, even though I have used universal references and here it is bound to Lvalue, the string is moved when I do std::forward<std::string>. Hence the in the last line, after "Main:" there is no output. Can someone please how the string is moved even though obj is bound to Lvalue.

Just realized the answer to this, after rereading a book.

In funcC, std::forward<std::string>(obj.m_) is equivalent to moving the string. But in funcD, std::forward is instantiated as std::forward<struct A&>(obj), which is copied.

avi007
  • 73
  • 1
  • 6
  • 1
    By the way, now it is called *forwarding reference*, not *universal reference*. – llllllllll Feb 19 '18 at 11:59
  • Since C++17 is it ? – avi007 Feb 21 '18 at 10:25
  • @avi007: [Basically, yes](https://cplusplus.github.io/EWG/ewg-complete.html#147), although aside from the standard this has been the common terminology for a few years now. The only actual standard change is to _add_ this name (the old name was never in there) so you can probably safely apply the newly-popular terminology when discussing code compliant to any of the standards since C++11. – Lightness Races in Orbit Feb 21 '18 at 11:09

1 Answers1

1

In both cases you are constructing a string, std::string local, with an rvalue reference as argument. As a result, local is move-constructed from the original object referred-to by that reference.

This doesn't have much to do with std::forward or with std::move; furthermore, you are not initialising a reference (which is where "binding a reference to an lvalue" comes from in Meyers's text; the reference would be on the LHS) — you are simply constructing an object from another object.

However, it is true that without writing std::forward or std::move you would end up copying those function arguments instead, as the rvalue reference would be dropped on the initialisation side of local's declaration.

So, in this case, std::forward and std::move have the same effect. However, they are not the same thing and should generally not be treated as interchangeable. Read the following for more information:

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Don't mean to be upfront. But I'm currently studying Effective modern C++. I can see phrases like "binding to an lvalue" and "binding to rvalue" almost everywhere in chapter 5. obj in funcC is universal reference, and I'm passing obj2 in main. So it is getting instantiated with normal reference i.e Lvalue. – avi007 Feb 20 '18 at 09:26
  • @avi007: Okay, assuming Meyers, I see where you're getting that from. The book is referring to binding references to lvalues, i.e. initialising references from lvalues. Not the other way around (which I maintain doesn't exist). I've tweaked my answer a little to clarify. – Lightness Races in Orbit Feb 20 '18 at 10:54
  • I have just added funcD, and it's corresponding output in the question. Could you please comment on how it's different from funcC. – avi007 Feb 21 '18 at 08:14
  • Just going through, Effective Modern C++ again. So according to Item 28, in funcD, forward is instantiated as forward, so the output makes sense. Thanks Lightness Races in Orbit – avi007 Feb 21 '18 at 10:20