What Is "Rvalue Reference For *This"

What is rvalue reference for *this?

First, "ref-qualifiers for *this" is a just a "marketing statement". The type of *this never changes, see the bottom of this post. It's way easier to understand it with this wording though.

Next, the following code chooses the function to be called based on the ref-qualifier of the "implicit object parameter" of the function:

// t.cpp
#include <iostream>

struct test{
void f() &{ std::cout << "lvalue object\n"; }
void f() &&{ std::cout << "rvalue object\n"; }
};

int main(){
test t;
t.f(); // lvalue
test().f(); // rvalue
}

Output:

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object

The whole thing is done to allow you to take advantage of the fact when the object the function is called on is an rvalue (unnamed temporary, for example). Take the following code as a further example:

struct test2{
std::unique_ptr<int[]> heavy_resource;

test2()
: heavy_resource(new int[500]) {}

operator std::unique_ptr<int[]>() const&{
// lvalue object, deep copy
std::unique_ptr<int[]> p(new int[500]);
for(int i=0; i < 500; ++i)
p[i] = heavy_resource[i];

return p;
}

operator std::unique_ptr<int[]>() &&{
// rvalue object
// we are garbage anyways, just move resource
return std::move(heavy_resource);
}
};

This may be a bit contrived, but you should get the idea.

Note that you can combine the cv-qualifiers (const and volatile) and ref-qualifiers (& and &&).


Note: Many standard quotes and overload resolution explanation after here!

† To understand how this works, and why @Nicol Bolas' answer is at least partly wrong, we have to dig in the C++ standard for a bit (the part explaining why @Nicol's answer is wrong is at the bottom, if you're only interested in that).

Which function is going to be called is determined by a process called overload resolution. This process is fairly complicated, so we'll only touch the bit that is important to us.

First, it's important to see how overload resolution for member functions works:

§13.3.1 [over.match.funcs]

p2 The set of candidate functions can contain both member and non-member functions to be resolved against the same argument list. So that argument and parameter lists are comparable within this heterogeneous set, a member function is considered to have an extra parameter, called the implicit object parameter, which represents the object for which the member function has been called. [...]

p3 Similarly, when appropriate, the context can construct an argument list that contains an implied object argument to denote the object to be operated on.

Why do we even need to compare member and non-member functions? Operator overloading, that's why. Consider this:

struct foo{
foo& operator<<(void*); // implementation unimportant
};

foo& operator<<(foo&, char const*); // implementation unimportant

You'd certainly want the following to call the free function, don't you?

char const* s = "free foo!\n";
foo f;
f << s;

That's why member and non-member functions are included in the so-called overload-set. To make the resolution less complicated, the bold part of the standard quote exists. Additionally, this is the important bit for us (same clause):

p4 For non-static member functions, the type of the implicit object parameter is

  • “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier

  • “rvalue reference to cv X” for functions declared with the && ref-qualifier

where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration. [...]

p5 During overload resolution [...] [t]he implicit object parameter [...] retains its identity since conversions on the corresponding argument shall obey these additional rules:

  • no temporary object can be introduced to hold the argument for the implicit object parameter; and

  • no user-defined conversions can be applied to achieve a type match with it

[...]

(The last bit just means that you can't cheat overload resolution based on implicit conversions of the object a member function (or operator) is called on.)

Let's take the first example at the top of this post. After the aforementioned transformation, the overload-set looks something like this:

void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'

Then the argument list, containing an implied object argument, is matched against the parameter-list of every function contained in the overload-set. In our case, the argument list will only contain that object argument. Let's see how that looks like:

// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
// kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
// taken out of overload-set

If, after all overloads in the set are tested, only one remains, the overload resolution succeeded and the function linked to that transformed overload is called. The same goes for the second call to 'f':

// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
// taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
// kept in overload-set

Note however that, had we not provided any ref-qualifier (and as such not overloaded the function), that f1 would match an rvalue (still §13.3.1):

p5 [...] For non-static member functions declared without a ref-qualifier, an additional rule applies:

  • even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
struct test{
void f() { std::cout << "lvalue or rvalue object\n"; }
};

int main(){
test t;
t.f(); // OK
test().f(); // OK too
}

Now, onto why @Nicol's answer is atleast partly wrong. He says:

Note that this declaration changes the type of *this.

That is wrong, *this is always an lvalue:

§5.3.1 [expr.unary.op] p1

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

§9.3.2 [class.this] p1

In the body of a non-static (9.3) member function, the keyword this is a prvalue expression whose value is the address of the object for which the function is called. The type of this in a member function of a class X is X*. [...]

What are rvalue references for *this for?

When called, each member function has an implicit object parameter that *this references.

So (a) these normal function overloads:

void f(const T&);
void f(T&&);

when called like f(x); and (b) these member function overloads:

struct C
{
void f() const &;
void f() &&;
};

when called like x.f() - both (a) and (b) dispatch with similar viability and ranking.

So the use cases are essentially the same. They are to support move semantic optimization. In the rvalue member function you can essentially pillage the objects resources because you know that it is an expiring object (is about to be deleted):

int main()
{
C c;
c.f(); // lvalue, so calls lvalue-reference member f
C().f(); // temporary is prvalue, so called rvalue-reference member f
move(c).f(); // move changes c to xvalue, so again calls rvalue-reference member f
}

So for example:

struct C
{
C operator+(const C& that) const &
{
C c(*this); // take a copy of this
c += that;
return c;
}

C operator+(const C& that) &&
{
(*this) += that;
return move(*this); // moving this is ok here
}
}

Rvalue References in C++ with an easy example

First, whether or not something resides in the memory or in a register is not defined by the standard. You don't need to think about it. For you, all objects work as if they were in memory. Under the as-if rule, your compiler can put some of them into the registers, as long as it doesn't affect how your program works.

Yes, you can obtain addresses of lvalues (except bitfields) and not rvalues, but that's a language design choice rather than a technical limitation (most of the time it would make no sense, so it's not allowed). Value categories (lvalues vs rvalues) have nothing to do with how things are stored in memory.

Im trying to understand what exactly Rvalue References are

Rvalue references are similar to lvalue references. The primary difference is what you can initialize them with. Rvalue references must be initialized with rvalues, and lvalue references must be initialized with lvalues (as an exception, const lvalue references are allowed to bind to rvalues).

int&& a = 50; works because 50 is an int and a temporary object. (Usually "a temporary object" is a synonym for "an rvalue", but not always.)

This case is a bit tricky, because temporaries as normally destroyed at the end of full-expression (roughly, when the line of code they were created in finishes executing). But here, the lifetime of 50 is extended because it's bound to a reference.

is b any different than a?

int&& a = 50;  
int b = 60;

Because of the lifetime extension - no, they're equivalent. Maybe there's some obscure difference between them, but I can't think of any.

rvalue reference or forwarding reference?

It's an rvalue reference. Forwarding references can only appear in a deduced context. This is just a member function that accepts an rvalue reference to the class template parameter.

You can't force a forwarding reference to be an rvalue reference if you want to maintain template argument deduction for functions. If you don't mind specifying the template argument all over the place, then this will always and only ever give an rvalue reference:

template<typename T> struct identity { using type = T; };

template<typename T> void func(typename identity<T>::type&&);

In retrospect, there actually is a way to maintain deduction but force only rvalue refs to be accepted (besides the self documenting one in Simple's answer). You can provide a deleted lvalue overload:

template<typename T>
void func(T&) = delete;

template<typename T>
void func(T&& s)
{
// ...
}

The lvalue overload is more specialized when passed an lvalue. And on account of being deleted, will give a somewhat clear error message.

Rvalue reference or lvalue?

The confusion is probably arising from the difference between r-value and r-value reference. The former is a value-category which only applies to expressions, while the latter is a type which applies to variables (technically it would need to be an r-value reference of some type, e.g. r-value reference to int).

So the difference between the snippets you've shown is not actually related to the type of the variable, but the value-category of the expression. Postfix operator++ requires the value-category of the operand to be an l-value, regardless of the type of the operand.

In k++, the expression k is an l-value (roughly speaking, it has a name), which is its value-category. The type of the variable k is an r-value reference, but that's fine.

In (static_cast<int&&>(3))++, the expression static_cast<int&&>(3) is an r-value (it doesn't have a name), which is its value-category. Regardless of the type of static_cast<int&&> (which is int), the value-category is wrong, and so you get an error.

Note that the error message using rvalue as lvalue is referring to the value-category of the expression being used. It has nothing to do with the types of the variables.

C++: rvalue reference memory

Numeric literals can't be bound to any reference, neither an rvalue reference nor an lvalue reference. Conceptually, a numeric literal creates a temporary object initialized from the literal value and this temporary can be bound to an rvalue references or to const lvalue reference (int const& r = 17;). It seems the relevant quote on literals is 5.1.1 [expr.prim.general] paragraph 1:

A literal is a primary expression. Its type depends on its form (2.14). A string literal is an lvalue; all other literals are prvalues.

When binding a reference directly to a temporary, it's life-time gets extended until the reference goes out of scope. The relevant section for the life time issue is 12.2 [class.temporary] paragraph 5:

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists
for the lifetime of the reference except:

  • A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.
  • A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.
  • The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
  • A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer.

What is the difference between rvalue reference and xvalue?

To answer the titular question, "rvalue reference" is a kind of type, while "xvalue" is a kind of expression.

rvalue references are considered lvalue (this part I understand)

They are not. Rvalue references are types, types are not expressions and so cannot be "considered lvalue". What you're referring to is the fact that if an expression consists solely of the name of a variable of type T&&, then it is an lvalue expression of type T.

why calling funcB() which return rvalue reference is considered rvalue

The most straightforward answer would be "by definition". A function call expression where the function's return type is T&& is an xvalue expression of type T. As for motivation, this is exactly what makes std::move do what it does: imbue any expression with the ability to be moved from (also known as "rvalue category" - see http://en.cppreference.com/w/cpp/language/value_category ).

What are the rules about using an rvalue reference on the left side of an equals sign?

Because C++ treats class types and build-in types in different ways.

For build-in types, rvalues can't be assgined.

For class types, e.g. std::string, test2(h) = "hello"; is just same as test2(h).operator=("hello");; operator= is the member of std::string, it's not special with other member functions. This is valid, if the member operator= is allowed to be called on an rvalue, and this is true for std::string::operator=. You can even write something like std::string{} = "hello";, i.e. assign to a temporary which will be destroyed soon, which doesn't make much sense indeed.

If you want to constrain the member function of user-defined class can only be called on lvalues, you can specify lvalue ref-qualifier (since C++11), or vice versa. e.g.

struct X {
X& operator=(const char*) & { return *this; }
// ^
};

LIVE

What is an rvalue reference to function type?

I hate to be circular, but an rvalue reference to function type is an rvalue reference to function type. There is such a thing as a function type, e.g. void (). And you can form an rvalue reference to it.

In terms of the classification system introduced by N3055, it is an xvalue.

Its uses are rare and obscure, but it is not useless. Consider for example:

void f() {}
...
auto x = std::ref(f);

x has type:

std::reference_wrapper<void ()>

And if you look at the synopsis for reference_wrapper it includes:

reference_wrapper(T&) noexcept;
reference_wrapper(T&&) = delete; // do not bind to temporary objects

In this example T is the function type void (). And so the second declaration forms an rvalue reference to function type for the purpose of ensuring that reference_wrapper can't be constructed with an rvalue argument. Not even if T is const.

If it were not legal to form an rvalue reference to function, then this protection would result in a compile time error even if we did not pass an rvalue T to the constructor.



Related Topics



Leave a reply



Submit