Example of lvalues / rvalues yielded by member access operators - c++

I have found multiple questions regarding the lvalue yielded by the arrow operator and the lvalue or rvalue yielded by the dot operator, but the answers are not quite coherent.
Chapter 4.6 of the book C++ Primer says:
The arrow operator requires a pointer operated and yields an lvalue. The dot operator yields an lvalue if the object from which the member is fetched is an lvalue; otherwise, the result is an rvalue.
Does the author only mean data members, or also member functions that can be used as an lvalue? Can someone give me an example where the behaviour of these two operators differs, and explain why that is?
Please excuse me if this has been answered elsewhere.

The quote is much easier to understand with some examples. I'll start with the "dot" operator first.
The "dot" operator
Lets consider data members first with a simple Person example:
struct Person {
std::string name;
};
In general when people use a struct or class, you'll normally see it accessed through a named-value which will always yield the standard/expected lvalue reference:
auto person = Person{"Foo"}
std::string& name = person.name;
However, this doesn't always have to be an lvalue. This member access through the "dot" operator will propagate the refness of the object being accessed. That means that you can implicitly get an rvalue ref of name if person is an rvalue (XValue):
auto person = Person{"Foo"};
std::string&& name = std::move(person).name;
Try it Online
or as a temporary (PRValue):
// Note: using a function call here so that the reference doesn't dangle
auto consume(std::string&&) -> void;
...
consume( Person{"Foo"}.name );
Try it Online
This is what the quote is referring to when it says:
The dot operator yields an lvalue if the object from which the member is fetched is an lvalue; otherwise, the result is an rvalue.
This value-propagation is somewhat true of member functions as well. Since C++11, it's been possible to ref-qualify member functions -- which will call the appropriate function based on the refness of the underlying object:
class Person {
...
auto get_name() && -> std::string&&;
auto get_name() const & -> const std::string&;
...
private:
std::string m_name;
};
...
auto person = Person{...};
person.get_name(); // calls 'get_name() const &'
std::move(person).get_name(); // calls 'get_name() &&'
Try it Online
Note: Unlike accessing a data member, the result of the function call need not actually match the value-category; this is just used for determining which function to call as a form of overload resolution.
Arrow Operator
For the default arrow operator (e.g. not a custom operator->), it can only operate on pointers. Pointers, unlike references, don't contain the underlying value-category of the pointee; they only know the value category of the pointer itself.
For example, std::moveing a Person* will yield a Person*&& -- which is an rvalue of the pointer, but not of the underlying pointed object. For this reason, dereferencing through operator-> can never yield an rvalue, since the pointee is never known to be an rvalue.
Custom operator-> definitions
The Arrow operator is also interesting because, for a custom class, it can be overloaded by defining operator->() -- but this operator will still be constrained by the same problem as described above!
Any definition of operator-> must return an object which either defines operator->, or is a pointer. This is done because any expression a->b in C++ will recursively call operator-> until it reaches a pointer in order to perform the dereference. As we know from above, a pointer does not contain a value category -- so once we reach this point we cannot get an rvalue.
Even if you were to attempt to combine this with ref-qualified functions, such as:
auto operator->() && -> SomeWrapper<T>;
you still won't be able to propagate the value-category to the reference because eventually it will come back down to a pointer which does not contain a value category.

Preface: the C++ system of value categories is rather abstruse; the terms 'lvalue' and 'rvalue' really aren't the whole story, so if you want the full explanation of value categories in all its technical glory, I advise you to check out https://en.cppreference.com/w/cpp/language/value_category as #Richard Critten said in the question comments.
Now, supposing you have a struct
struct S{
int member {5};
};
, the behaviour of the operators will differ when performing member access on temporary objects—since their addresses cannot be taken, member access can only take the form of the dot operator (since no pointer can be produced for the arrow operator):
int x = S().member; // Ok, x = 5
int x = (&(S()))->member; // Error: cannot take address of temporary object
In this case, S() is an rvalue expression, and we can use the dot operator on it, but there isn't really a way to use the arrow operator on it.
Regarding "member functions that can be used as an lvalue", I'm assuming you mean functions that return an lvalue reference, e.g.
struct S {
int member {5};
int& get_ref()
{
return member;
}
}
where the returned lvalue reference can be written to; I don't believe that there is any functional difference with the normal member access case.

Related

Beginner Question: C++ Pointer/Addresses - & after variable not before?

I'm a rusty amateur C++ programmer coming back after along time and moving on from OpenGL 2.0 to Vulkan.
I'm trying to understand the code behind the tutorials I'm reading not just copy, but don't understand the & part of the following:
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
Why is the & after exception? My understanding is the pointer is usually after the variable and the address before it, such as:
int* pintVar;
pintVar = &intVar;
Has this got something to do with binding a constant reference 'exception' to a temporary object 'e'?
As you can see I'm trying to research/google it but not totally understanding it or why.
Any help would be appreciated.
& has different meanings depending on context. In a declaration, it means reference to. e.g.
int a = 42;
int &b = a; // b is a reference to a
// b is essentially an alias of a (they both have the same address, for example)
// i.e. changes to b will be reflected in a, and vice versa
Here's some reference that explains this in more depth.
When & is used on an already existing variable, this means address-of. e.g.
int a = 42;
int *b = &a; // b points to a (i.e. b holds the address of a).
So in your example, e is a const reference to whatever exception is passed in (since the parameter of a function declares a variable).
Analogous question:
int* pintVar;
"Why is the * after int? What is the indirection operator doing there? Why isn't it before the variable name such as in here?"
int intVar = *pintVar;
Well, you see * symbol has multiple meanings. Perhaps you already understand this: Within a compound type name, it signifies a pointer type. Within an expression it signifies an operator. It can be either the unary indirection operator (*pintVar) or the binary multiplication operator (a * b).
In the exactly same way, & symbol has multiple meanings. Within a compound type name, it signifies a reference type. Within an expression it signifies an operator. It can be either the unary addressof operator (&intVar) or the binary bitwise AND operator (a & b).
So, const T& is a reference to const T, just like const T* would be a pointer to const T.
Both reference and pointer are forms of indirection, and are quite smilar. Their differences are:
Pointers are objects, references are not objects
Because references are not objects, there is no way to take the address of a reference, and there is no such thing as a pointer to reference or a reference to reference.
There are also no arrays of references.
A (non-const) pointer can be assigned to point to another object. A reference is bound to a single object for its entire lifetime.
Pointer can be null, a reference cannot be.
You must indirect through a pointer explicitly using an indirection operator. All operations on a reference implicitly indirect through the reference, and apply to the referred object instead.
Assignment of reference is assignment of the referred object.
Taking the address of a reference variable is taking address of the referred object.
Pointers are iterators for arrays; You can do pointer arithmetic to iterate the elements. Adding one to a pointer results in pointer to the successive array element. There is no reference arithmetic. Because of the implicit indirection, adding one to reference adds one to the referred object.
Has this got something to do with binding a constant reference 'exception' to a temporary object 'e'?
There are many right words there, but I don't understand what you are trying to mean by them.
e is a variable. The type of the variable is const std::exception& i.e. reference to constant exception. Upon throw, the reference is bound to the exception object that was thrown.

how can const_cast<const string &>(s), while s is a string type?

I asked a related, tedious question before and now I discover something new.
#include <iostream>
#include <string>
using namespace std;
void hello (const string &s1) {
cout << "rocky" << endl;
}
void hello(string &s1) {
cout << "banana" << endl;
}
int main()
{
string s = "abc";
hello(const_cast<const string&>(s)); //how to explain this const_cast?
}
hello(const_cast<const string&>(s)); this works and match the const reference parameter function. So how's the conversion this time? Isn't it string to const string&?
Certainly I know that const reference can be initialized with non-const objects... But somehow I never take that as a conversion. I see it as an assignment. And I consider the type of reference and the type of referent as two very different stuff.
So, primary meaning for the cast is to pick necessary one from the list of overloaded hello() functions. Without the cast, we pick non-const version, otherwise it's const one.
Secondly, why the cast of the string reference, not just string type? This is due limitations of the const_cast itself. Let's try to compile that:
hello(const_cast<const string>(s)); // we removed the "&"
compiler message:
error: invalid use of const_cast with type ‘const string {aka const std::__cxx11::basic_string<char>}’,
which is not a pointer, reference, nor a pointer-to-data-member type
So, const_cast is not intended to create new instance, instead it works with indirection to given instance and just changes associated CV qualification (as it's free lunch in terms of code generation). So, we have to deal with a pointer or reference to conform that condition.
P.S. As far as I know, C++17 allows creation of temporary copies for the casting (aka Temporary materialization) so our non-ref attempt could have practical meaning. Same time, it's quite new concept and not so wide-spread.
Expressions, objects, variables - I know C++ can be a bit confusing to newcomers, especially when it comes to the subtle details.
So s here is a variable in main. While main runs (and that's the whole program, since it's main), there is an object that corresponds to the variable. And in main, the expression s refers to to that object. But there are many more expressions that refer to the same object. (s) is another expression that refers to the same object. So is *&s. Or *(&s).
s+"" is another expression that has the same value. But it's not the same object. Cf. integers i+0 or i*1.
References can be used to define variables which introduce new names for objects, names that can then be used in expressions. There are many places where it's convenient to be able to explicitly name something. It's just a new name, not a new object.
const_cast is another use for references. This however doesn't introduce a new name; it just forms a new expression with a const added to the type.
Now to your hello. This is an overloaded function. Overload resolution is done on the type of the expression used as an argument. So in hello(s1), s1 is the simple expression used for overload resolution, and its type is std::string. In hello(const_cast<std::string const&>(s1)), the expression is const_cast<std::string const&>(s1), and its type is obviously std::string const&.
Both expressions refer to the same object; they only differ in type. But that type difference is exactly what overload resolution needs.
You can only convert from const reference to lvalue reference explicitly by using const_cast as such conversion is not safe and must be explicit. On another side you can convert from lvalue or lvalue reference to const reference implicitly or explicitly, as such conversion is safe. So usually nobody cast to const reference explicitly as it is unnecessary verbose and does not make sense. In your case it is converted explicitly to select certain overloaded function, because compiler would not convert it implicitly, as it can find overload with lvalue reference, but your example is rather artificial, as it is difficult to imagine why somebody would have such overload.

address of this

I try to find address of this pointer, but this code is showing a strange
error:
#include <iostream>
using namespace std;
class Base
{
public:
void test()
{
void *address_of_this =&this;
cout<<address_of_this<<endl;
}
};
int main()
{ Base k;
k.test();
return 0;
} //error non-lvalue in unary'&'
Can you explain this error ?
Also point that what is illegal in taking address of this?
this is a pointer containing the address to the "current object". It is not a variable that is stored somewhere (or could even be changed), it is a special keyword with these properties.
As such, taking its address makes no sense. If you want to know the address of the "current object" you can simply output:
std::cout << this;
or store as
void* a = this;
Quoting the 2003 C++ standard:
5.1 [expr.prim] The keyword this names a pointer to the object for which a nonstatic member function (9.3.2) is invoked. ... The type of the expression is a pointer to the function’s class (9.3.2), ... The expression is an rvalue.
5.3.1 [expr.unary.op] The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified_id.
To put it simply, & requires an lvalue. this is an rvalue, not an lvalue, just as the error message indicates.
this refers to the current object by using it's address.
In your problem, there are two errors:
this is not an lvalue.
The & requires an lvalue. lvalues are those that can appear on on the left-hand side of an assignment (variables, arrays, etc.).
Whereas this is a rvalue. rvalues can not appear on the left-hand side (addition, subtraction, etc.).
Reference: C++ Rvalue References Explained.
A hidden error which I'd like to also mention is thus:
address_of_this is actually receiving an address of an address.
Basically, &this is translated into something like &&object or &(&object).
Basically, think of this as &object (but only to remember because it is not that true).

C++ Primer Plus this pointer example

Sorry for turning to here for such a basic question, but can someone just quickly clear this up for me? I'll then delete the thread so as not to cause noob clutter.
In the following example from the C++ Primer Plus text, doesn't the & operator in the function declaration designate that the function returns a pointer to a Stock object? Why then does the function proceed to return the s and this objects by value instead?
"...What you want to return, however, is not this, because this is the address of the object. You want to return the object itself, and that is symbolized by *this. (Recall that applying the dereferencing operator * to a pointer yields the value to which the pointer points.) Now you can complete the method definition by using *this as an alias for the invoking object."
const Stock & Stock::topval(const Stock & s) const {
if (s.total_val > total_val)
return s; // argument object
else
return *this; // invoking object
}
Yeah, that's confusing. C++ massively overloads every symbol, because there just aren't enough symbols on the keyboard.
The ampersand & is used for two different meanings which are conceptually similar, but are actually completely different language features.
Meaning 1: Reference type declaration. Append an ampersand to type A which means a-reference-to-type-A. Example:
Stock x;
Stock& s = x; // now s is a reference to x
Meaning 2: Address-of operator. A unary operator that returns a pointer to its argument. Example:
Stock x;
Stock* s = &x; // now s a pointer to x
Reminder: References and pointers are exactly the same thing, except they have different syntax, and references can never be null, and you can't have a reference to a reference.
Don't delete this thread, we love n00bs. I'm a n00b myself.
const Stock & means return a const reference to an object. A pointer to an object would be const Stock *. Don't mix the two! So the this pointer is being dereferenced and returned.

so what is the type of "this" ? Why is "this" not a lvalue?

Say the object is
class A {
public : void Silly(){
this = 0x12341234;
}
I know I will get compiler error ' "this" is not a lvalue.' But then it is not a temporary either. So what is the hypothetical declaration of "this" ?
Compiler : GCC 4.2 compiler on mac.
For some class X, this has the type X* this;, but you're not allowed to assign to it, so even though it doesn't actually have the type X *const this, it acts almost like it was as far as preventing assignment goes. Officially, it's a prvalue, which is the same category as something like an integer literal, so trying to assign to it is roughly equivalent to trying to assign a different value to 'a' or 10.
Note that in early C++, this was an lvalue -- assigning to this was allowed -- you did that to handle the memory allocation for an object, vaguely similar to overloading new and delete for the class (which wasn't supported yet at that time).
It is impossible to provide a "declaration" for this. There's no way to "declare" an rvalue in C++. And this is an rvalue, as you already know.
Lvalueness and rvalueness are the properties of expressions that produce these values, not the properties of declarations or objects. In that regard, one can even argue that it impossible to declare an lvalue either. You declare an object. Lvalue is what is produced when you use the name of that object as an expression. In that sense both "to declare an rvalue" and "to declare an lvalue" are oxymoron expressions.
Your question also seems to suggest that the properties of "being an lvalue" and "being a temporary" are somehow complementary, i.e. everything is supposedly an lvalue or a temporary. In reality, the property of "being a temporary" has no business being here. All expressions are either lvalues or rvalues. And this happens to be an rvalue.
Temporaries, on the other hand, can be perceived as rvalues or as lvalues, depending on how you access the temporary.
P.S. Note, BTW, that in C++ (as opposed to C) ordinary functions are lvalues.
For one thing, this is not a variable - it's a keyword. When used as a rvalue, its type is A * or A const *. In modern C++, assigning to this is prohibited. You cannot take the address of this, either. In other words, it's not a valid lvalue.
To answer the second part, "why is this not an lvalue", I'm speculating as to the committee's actual motivation, but advantages include:
assigning to this doesn't make much logical sense, so there's no particular need for it to appear on the left-hand-side of assignments. Making it an rvalue emphasises that this doesn't make much sense by forbidding it, and means that the standard doesn't have to define what happens if you do it.
making it an rvalue prevents you taking a pointer to it, which in turn relieves the implementation of any need to furnish it with an address, just like a register-modified automatic variable. It could for example devote a register in non-static member functions to storing this. If you take a const reference, then unless the use permits cunning optimization it needs to be copied somewhere that has an address, but at least it needn't be the same address if you do it twice in quick succession, as it would need to be if this were a declared variable.
You get a compiler error because this is a const pointer to the instance of the class of the same type as that class. You can't assign to it although you can use it to change other class members in non-const qualified methods, call methods, and operators. Also note because it's an instance that static methods do not have a this pointer.
Hypothetical:
class Whatever
{
// your error because this is Whatever* const this;
void DoWhatever(const Whatever& obj) { this = &obj; }
// this is ok
void DoWhatever(const Whatever& obj) { *this = obj; }
// error because this is now: const Whatever* const this;
void DoWhatever(const Whatever& obj) const { *this = obj; }
// error because this doesn't exist in this scope
static void DoWhatever(const Whatever& obj) { *this = obj; }
};