Understanding the creature created by static_cast<type&&>(x)

Pages: 12
Jul 17, 2017 at 11:20pm
From http://en.cppreference.com/w/cpp/language/static_cast

If new_type is an rvalue reference type, static_cast converts the value of glvalue, class prvalue, or array prvalue any lvalue expression to xvalue referring to the same object as the expression


That underlined sentence!

I created a struct with a constructor and a copy constructor.

1
2
3
4
5
Obj o1;    // calls constructor

Obj o2 = static_cast<Obj&&>(o1);  // calls copy constructor

static_cast<Obj&&>(o1);   // calls nothing 


The temporary object that static_cast returns has the same properties of the object o1.

This temporary must have been created in some way!

Look:

 
Obj a = Obj{};


The code above calls BOTH the constructor for the temporary AND the copy constructor for the lvalue

So, in this example, before constructing a the temporary was constructed first.

---

In the first code, instead, no temporary has actually been created because the constructor for it isn't called.


I'm fully aware that, if a Move Assignment Operator had been overloaded, it would've been called and the resources held by o1 could have been moved to o2 leaving o1 in a "moved-from", empty state.

What I'm actually trying to understand is what happens to o1 when static_cast<Obj&&>(o1) is invoked on him?

A trivial answer would be: it is just casted to a temporary
But what does it mean!?!?

Another trivial answer would be: it just returns an rvalue reference that refers to o1
But (1) rvalue reference cannot bind to lvalue and (2) if that was true I couldn't do

 
Obj o2 = static_cast<Obj&&>(o1);  


what happens to o1 when static_cast<Obj&&>(o1) is invoked on him?
Last edited on Jul 17, 2017 at 11:53pm
Jul 18, 2017 at 6:17am
what happens to o1 when static_cast<Obj&&>(o1) is invoked on him?
Nothing. It is just the sign for the compiler that the move constructor/operator can be called if existing. The operator of o2 however might modify o1.
Jul 18, 2017 at 6:20am
> static_cast<Obj&&>(o1);
> The temporary object that static_cast returns has the same properties of the object o1.
> This temporary must have been created in some way

A reference is not an object. (It need not occupy any storage, we can't take its address, so there are no pointers to references, we can't create arrays of references etc.). There is no temporary object that is created as a result of evaluating this cast.


> what happens to o1 when static_cast<Obj&&>(o1) is invoked on him?

Absolutely nothing happens to o1.
The result of the static cast is an alias for the object identified by o1; the value category of this alias is xvalue. This alias by itself is not an object (it is a reference).
Jul 18, 2017 at 11:07am
The result of the static cast is an alias for the object identified by o1; the value category of this alias is xvalue. This alias by itself is not an object (it is a reference).


Are you telling me that static_cast creates some kind of temporary reference?

Never heard of such a thing!
Jul 18, 2017 at 12:07pm
Are you telling me that static_cast creates some kind of temporary reference?
No, a cast itself does neither create nor change anything.
Jul 18, 2017 at 1:23pm
> Are you telling me that static_cast creates some kind of temporary reference?
> Never heard of such a thing!

Never heard of such a thing for a very good reason: we can only talk about the life-time of objects.

The materialisation of a temporary object may occur when we use the result of a cast expression.
For example:
1
2
3
4
5
6
7
const char* cstr = "hello world!" ;

// implicit temporary materialisation
std::cout << ( "message: " + static_cast<std::string>(cstr) ).rbegin()[5] << '\n' ; // w

// explicit temporary materialisation
std::cout << ( "message: " + static_cast< std::string&& >(cstr) ).rbegin()[5] << '\n' ; // w 


However, there is no temporary object that is involved here:
If it aids our understanding, at best we can think of the result of the cast as a "reference that is not associated with a variable".
1
2
3
4
5
6
7
std::string str = "hello world!" ;

// no temporary object is involved in this
std::cout << ( "message: " + static_cast<std::string&>(str) ).rbegin()[5] << '\n' ; // w

// no temporary object is involved in this
std::cout << ( "message: " + static_cast< std::string&& >(str) ).rbegin()[5] << '\n' ; // w 
Jul 18, 2017 at 2:26pm
In your example, you used static_cast<string&> which evaluates to an lvalue.

To my brain, these two codes are the exact same thing:

1
2
3
4
5
6
7
// I created a (unnamed) reference and modified var through it
int var = 10;
static_cast<int&>(var) = 20;

// I created a (named) reference and modified var through it
int var = 10;
int& ref = var = 20;


That follows the rule of what a reference is: an alias of an already existing object.

static_cast<type&&>, however, seems to not take part in the party.

I'm sure I'll better understand what static_cast<type&&> does if we use a different terminology: computer's memory simplified graphical representation.

Take a look here: http://i.imgur.com/UOQ1Gf0.png

That purple static_cast<type&&> is too silent, too sneaky-peaky!

Unlike the static_cast, the lvalue reference ref did something in the computer's memory simplified representation: it stands near the var memory location meaning that they both refer to the same location.

ref's role is transparent

I want a verbose understanding of what static_cast<type&&> does, and how he manages to "transform" my lvalue into an xvalue/rvalue


I want static_cast<type&&> to become transparent too in this graphical representation!
Last edited on Jul 18, 2017 at 2:33pm
Jul 18, 2017 at 4:24pm
That follows the rule of what a reference is: an alias of an already existing object.
static_cast<type&&>, however, seems to not take part in the party.

it also is an alias to an already existing object, there is no difference in that respect.


it just returns an rvalue reference that refers to o1
But (1) rvalue reference cannot bind to lvalue and (2) if that was true I couldn't do
Obj o2 = static_cast<Obj&&>(o1);
what happens to o1 when static_cast<Obj&&>(o1) is invoked on him?

Nothing happens to o1. The sentence "rvalue reference cannot bind to lvalue" has nothing to do with this line of code. Is that the stumbling block? That sentence describes a limitation on reference initalization, which takes place when a named reference is declared or a function call is made to a function whose parameter is a reference.
Jul 19, 2017 at 11:03am
Hi Cubbi.

In the picture I made, what would happen to the computer memory when static_cast<type&&>() is called alone?

1) An unnamed rvalue reference is created?

I find it hard to believe. If an rvalue reference is created "on the fly", as every reference, it must reference to something at initialization time.
And the only thing an rvalue reference can reference is a temporary.
o1 is not a temporary

2) An unnamed temporary rvalue reference is created?

How can you even create a temporary reference? It's like...

1
2
int a = 10;
int&{a}  // temporary (lvalue) reference to a 


It's just weird, isn't it?

None of these explanations seems believable.

And YES, the standard just says:
static_cast converts the value of glvalue, class prvalue, or array prvalue any lvalue expression to xvalue referring to the same object as the expression


How can an xvalue refer to an object?
Last edited on Jul 19, 2017 at 11:05am
Jul 19, 2017 at 3:23pm
gedamial wrote:
In the picture I made, what would happen to the computer memory when static_cast<type&&>() is called alone?

nothing happens to the computer memory.

And the only thing an rvalue reference can reference is a temporary. o1 is not a temporary

no, rvalue reference variable can reference any object. The object does not need to be a temporary. What you can't do is initialize an rvalue reference variable with an lvalue expression using one of the syntax forms for reference initailization, such as T&& r = obj;. You're not doing that here.

1) An unnamed rvalue reference is created?
2) An unnamed temporary rvalue reference is created?

You're mixing up three different concepts: rvalue reference variables, rvalue reference types, and xvalue expressions.
static cast to rvalue reference type is an xvalue expression. It does not create anything. That xvalue expression may be used to initialize an rvalue reference variable, but that's beside the point.

How can an xvalue refer to an object?

That's all it can ever do. glvalues (lvalues and xvalues) are expressions that refer to objects.
Last edited on Jul 19, 2017 at 3:24pm
Jul 19, 2017 at 3:36pm
nothing happens to the computer memory.

oh... a bit disappointed

rvalue reference variable can reference any object.The object does not need to be a temporary.


This confuses me.
I knew that rvalue references can only bind to temporaries:

1
2
3
4
5
6
int f() { return 5; }

int var = 10;

int&& rv = var;   // error, var is an lvalue, not a temporary
int&& rv = f();    // ok, f() returns a temporary (prvalue) 


What you can't do is initialize an rvalue reference variable with an lvalue expression using one of the syntax forms for reference initailization


In fact, in the code above my attempt to initialize rv with an lvalue fails (obviously).

In which case then I can bind my rvalue reference to anything?


How can an xvalue refer to an object?
That's all it can ever do. glvalues (lvalues and xvalues) are expressions that refer to objects.


xvalues refer to objects? What?

I knew that xvalues are those that are returned from a static_cast<type&&> or a std::move()
Jul 19, 2017 at 3:48pm
I'll extend that example

1
2
3
4
5
6
int f() { return 5; }
int var = 10;
//int&& rv1 = var;   // error, var is an lvalue, not a temporary
int&& rv2 = f();    // ok, f() returns a temporary (prvalue)
int&& rv3 = static_cast<int&&>(var); // OK, rv3 references the (non-temporary) var
int&& rv4 = static_cast<int&&>(f());  // OK, rv4 references the temporary int 

Jul 19, 2017 at 3:51pm
and here's two more:
1
2
int&& rv5 = rv2; // error: rv2 is an lvalue
int&& rv6 = static_cast<int&&>(rv2); // OK, rv6 references the same temporary as rv2 
Jul 19, 2017 at 5:28pm
 
int&& rv3 = static_cast<int&&>(var); // OK, rv3 references the (non-temporary) var 



How can an rvalue reference (rv3) bind to a non temporary?
Jul 19, 2017 at 6:41pm
gedamial wrote:
How can an rvalue reference (rv3) bind to a non temporary?

just as shown. Why wouldn't it?
You can compare their addresses if that helps: assert(&rv3 == &var);

Jul 19, 2017 at 7:44pm
Mhm... is there another way to make an rvalue reference to reference the same object held by var without the static_cast?

Looks like only the static_cast can do this magic...
Jul 19, 2017 at 7:57pm
gedamial wrote:
Looks like only the static_cast can do this magic...

That "magic" is what makes std::move work, and that's half the reason rvalue references exist (the other half being std::forward). You can use std::move, it's better looking:
1
2
3
int var = 10;
int&& rv = std::move(var);
assert(&rv == &var);

Jul 19, 2017 at 8:09pm
So which one of these sentences is correct (if any):

1
2
3
int var = 10;

static_casy<int&&>(var)


1) static_cast<int&&>(var) creates a (temporary) rvalue reference and binds it to the value held by var

2) static_cast<int&&>(var) allows var to be treated as an rvalue reference instead of an lvalue where the cast is performed (like with static_cast<float>(int_var), with the only difference that casting to a reference doesn't instantiate an object)

---------------------------------

P.S: where does the official documentation say that an rvalue reference can refer to both temporaries and not temporaries? Until this thread, I knew rvalue reference could only bind to rvalues/temporary objects that are about to die and from which is safe to move

---------------------------------

P.S2: yes I I know the implementations of both std::move and std::forward, and they're basically casts (unconditional for move, conditional for forward)
Last edited on Jul 19, 2017 at 8:12pm
Jul 19, 2017 at 8:37pm
gedamial wrote:
So which one of these sentences is correct (if any):

#2 is the closest to the actual definition, which is that the cast is an xvalue expression that references/identifies/names-whatever the same object as var.

(it's close but not quite correct because "treated as an rvalue reference instead of an lvalue" sounds like "treated as fast instead of a chair" to me. Types and value categories are different things)

where does the official documentation say that an rvalue reference can refer to both temporaries and not temporaries?

Official documentation is hard to follow, which is why we're having this thread, but it actually has this example
1
2
int i2 = 42;
int&& rri = static_cast<int&&>(i2); // bound directly to i2 

you can find it in http://eel.is/c++draft/dcl.init.ref#5.2.1
Jul 19, 2017 at 8:49pm
Redefinition:

static_cast<int&&>(var) allows var to be treated as an rvalue reference to integer instead of an integer

Better now? :D Or can it be improved even more?

I'm sorry for being so picky ...... but I'm that kind of person that will never use something until he understands what it EXACTLY is and does.
Last edited on Jul 19, 2017 at 8:49pm
Pages: 12