Question about r-value reference with function call

I saw the sample code on Stack Overflow.
(https://stackoverflow.com/questions/3582001/advantages-of-using-forward)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void overloaded_function(const std::string& param) {
    std::cout << "const std::string& version: " << param << '\n';
}

// [II]
void overloaded_function(std::string&& param) {
    std::cout << "std::string&& version: " << param << '\n';
}

template <typename T>
void pass_through_f(T&& param) {
    overloaded_function(std::forward<T>(param));
}

int main() {
    std::string str = "Hello World";
    
    pass_through_f(str);            // (1)
    pass_through_f(std::move(str)); // (2)
    
    return 0;
}


If (2) is called, type of argument passed to 'param' is converted from std::string to std::string&& because of std::move(). But people said 'param' is l-value, not r-value because it has its own name. They say 'param' is re-casted by std::forward() and passed to overloaded_function()[II].

Then is 'param' l-value that has r-value reference type?
Plus, as I know, every functions' return value is treated as r-value. Is it correct? If so, Can I substitute std::move() in (2) to other function call to call overloaded_function()[II]?
l-value's are loosely recognized as those which can be on the left side of an assignment, i.e. can accept assignment.

They have a location in memory, and you can get their address.

Geniune r-value's are those which can only be on the right side because they can't accept assignment. r-value's are usually expressions which may be temporary, you might not be able to get the address of one and that may be because they are optimized to a compile time expressions (constexpr or related), and therefore either can't accept assignment or an assignment would be meaningless.

int & get_an_int();

If the code above is a member function, and it returns a reference to a member int, then it can be used as an l-value

get_an_int() = 5;

I offer that merely to point out an exception to your point about returns.

Now, more to your question. The return from std::move is declared in the documentation:

static_cast<typename std::remove_reference<T>::type&&>(t)

If you write a function that returns this type, it should operate like std::move for the purpose of proper selection of the overload.

The documentation describes this as an xvalue (meaning there's more than just lvalue and rvalue to consider).
Last edited on
> every functions' return value is treated as r-value. Is it correct?

No.

If the return type of the function is not a reference, the value category of the call expression is prvalue
If the return type of the function is an lvalue reference, the value category of the call expression is lvalue
If the return type of the function is an rvalue reference to object, the value category of the call expression is xvalue

More information: https://en.cppreference.com/w/cpp/language/value_category


Here is a similar snippet which generates annotated output; it may help in understanding of perfect forwarding:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <string>

void foo( std::string& )
{ std::cout << "1. non-const lvalue: void foo( std::string& )\n" ; }

void foo( const std::string& )
{ std::cout << "2. const lvalue: void foo( const std::string& )\n" ; }

void foo( std::string&& )
{ std::cout << "3. rvalue: void foo( const string&& )\n" ; }

template < typename T > void pass_through( T&& arg )
{
    const auto ref_type = [] { return std::is_lvalue_reference<T&&>::value ? "lvalue" : "rvalue" ; };

    std::cout << "void pass_through( T&& ) - forwarding "
              << ref_type() << " reference\n\t" ;

    foo( std::forward<T>(arg) ) ;
}

#define NOISY_CALL(actual_call) \
{\
    std::cout << "\ncall " << #actual_call << '\n' ;\
    actual_call ;\
    std::cout << '\n';\
}

int main()
{
    std::string non_const_str = "abcd";
    const std::string const_str = non_const_str ;

    NOISY_CALL( foo(non_const_str) )
    // call foo(non_const_str)
    // 1. non-const lvalue: void foo( std::string& )

    NOISY_CALL( foo(const_str) )
    // call foo(const_str)
    // 2. const lvalue: void foo( const std::string& )

    NOISY_CALL( foo( std::move(non_const_str) ) )
    // call foo( std::move(non_const_str) )
    // 3. rvalue: void foo( const string&& )

    NOISY_CALL( pass_through(non_const_str) ) ;
    // call pass_through(non_const_str)
    // void pass_through( T&& ) - forwarding lvalue reference
    //     1. non-const lvalue: void foo( std::string& )

    NOISY_CALL( pass_through(const_str) ) ;
    // call pass_through(const_str)
    // void pass_through( T&& ) - forwarding lvalue reference
    //        2. const lvalue: void foo( const std::string& )

    NOISY_CALL( pass_through( std::move(non_const_str) ) ) ;
    // call pass_through( std::move(non_const_str) )
    // void pass_through( T&& ) - forwarding rvalue reference
    //        3. rvalue: void foo( const string&& )
}

http://coliru.stacked-crooked.com/a/578acdae9afe0af2
https://rextester.com/KLXEQU59803
Topic archived. No new replies allowed.