Why does an assignment works as a condition? [duplicate] - c++

I am just starting C++. I am a bit confused about the return type of assignment and dereference operator. I am following the book C++ Primer. At various occasions, the author says that the return type of assignment operator is reference to the type of left hand operand but later on, he says that the return type is the type of the left hand operand. I have referred C++11 Standard Sec. 5.17, where the return type is described as "lvalue referring to left hand operand".
Similarly, I can't figure out whether dereference returns the pointed-to object or the reference to the object.
Are these statements equivalent? If so, then how? Any explanation would be appreciated.

The standard correctly defines the return type of an assignment operator.
Actually, the assignment operation itself doesn't depend on the return value - that's why the return type isn't straightforward to understanding.
The return type is important for chaining operations.
Consider the following construction: a = b = c;. This should be equal to a = (b = c), i.e. c should be assigned into b and b into a. Rewrite this as a.operator=(b.operator=(c)). In order for the assignment into a to work correctly the return type of b.operator=(c) must be reference to the inner assignment result (it will work with copy too but that's just an unnecessary overhead).
The dereference operator return type depends on your inner logic, define it in the way that suits your needs.

They can both be anything, but usually operator = returns the current object by reference, i.e.
A& A::operator = ( ... )
{
return *this;
}
And yes, "reference to the type of left hand operand" and "lvalue referring to left hand operand" mean the same thing.
The dereference operator can have basically any return type. It mostly depends on the logic of the program, because you're overloading the operator that applies to an object, not to a pointer to the object. Usually, this is used for smart pointers, or iterators, and return the object they wrap around:
struct smart_ptr
{
T* innerPtr;
T* smart_ptr::operator* ()
{
return innerPtr;
}
}
smart_ptr p;
T* x = *p;

I have seen similar issues, but I guess it would be best to use
X& X::operator=(const X&);
Using this, you will be able to reuse the object in a chain-assignment.

Related

Example of lvalues / rvalues yielded by member access operators

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.

Do all C++ operators return something?

All C++ operators that I have worked with return something, for example the + operator returns the result of the addition.
Do all C++ operators return something, or are there some C++ operators that do not return anything?
No, not all operators return something.
Although they are probably not exactly what you are thinking about, note that the delete and delete[] C++ 'keywords' are actually operators; and they are defined as having the void return type - which means they evaluate to nothing (which is not 'something').
From cppreference:
void operator delete ( void* ptr ) noexcept;
void operator delete[]( void* ptr ) noexcept;
Operators of custom types can be overloaded to do the most weirdest things.
for example the + operator returns the result of the addition.
Not necessarily:
#include <iostream>
struct foo {
int value = 0;
void operator+(int x) {
value += x;
}
};
int main () {
foo f;
f + 3;
}
Here operator+ adds the left hand side to the value member, and its return type is void. This is a made-up example, but, in general, not returning something from a custom operator is not unusual.
The only operator that can be overloaded and that has the requirement of returning something, that I am aware of, is operator->. It must either return a raw pointer or an object that has an operator->.
To nitpick, operators don't return anything. They are just lexical elements that we use to create expressions in the language. Now, expressions have types and may evaluate to values, and I assume this is what you mean by operators "returning things".
And, well, yes. There are C++ expressions with type void (and consequentially don't evaluate to any value). Some are obvious, others less so. A nice example would be
throw std::runtime_error()
throw is an expression under the C++ grammar. You can use it in other expressions, for instance in the conditional expression
return goodStatus() ? getValue() : throw std::runtime_error();
And the type of a throw expression, is void. Obviously since this just causes execution to rapidly go elsewhere, the expression has no value.
None of the built-in C++ operators return something. Overloaded C++ operators return something insofar that the operator notation is a syntactic sugar for a function call.
Rather, operators all evaluate to something. That something has a well-defined value as well as a type. Even the function call operator void operator()(/*params*/) is a void type.
For example, +'a' is an int type with the value of 'a' encoded on your platform.
If your question is "Can C++ operators have a void return type?" then the answer is most certainly yes.
You can actually define a function call operator to return nothing. For example:
struct Task {
void operator()() const;
};
operator void(): user defined conversion function to void
You may define the peculiar operator void() conversion function, where the compiler will even warn you that the T to void conversion function will never be used:
#include <iostream>
struct Foo {
operator void() { std::cout << "Foo::operator void()!"; }
// warning: conversion function converting 'Foo' to
// 'void' will never be used
};
int main() {
Foo f;
(void)f; // nothing
f.operator void(); // Foo::operator void()!
}
as governed by [class.conv.fct]/1
[...] A conversion function is never used to convert a (possibly
cv-qualified) object to the (possibly cv-qualified) same object
type (or a reference to it), to a (possibly cv-qualified) base class
of that type (or a reference to it), or to (possibly cv-qualified)
void.117
(117)
These conversions are considered as standard conversions for the
purposes of overload resolution ([over.best.ics], [over.ics.ref]) and
therefore initialization ([dcl.init]) and explicit casts. A
conversion to void does not invoke any conversion function
([expr.static.cast]). Even though never directly called to perform a
conversion, such conversion functions can be declared and can
potentially be reached through a call to a virtual conversion function
in a base class.
Whilst, however, as is shown above, you can still invoke it using the explicit .operator void() syntax.
The operators defined (builtin) by the language are tokens used to perform different kinds of computations:
arithmetic (+,-,*,/)
increment/decrement (++,--)
assignment (=,+=,-=,*=,/=,%=,>>=,<<=,&=,^=,|=)
logic (!,&&,||)
relational (==,!=,>,<,>=,<=)
conditional ?
comma
and so on. The list can be very extensive.
In language references like this one or this one, these are not necessarily referenced as returning something, just performing an arithmetic or logic operation, a comparison by which means a variable may be modified, etc.
Since this operation results in some value, it may be interpreted as been "returned" by the operator, but it is different from a function return value.
The overloaded operators, on the other hand, can be defined with a return value of some type, even that can be void, so, no, not all operators return some value in C++.
I'm assuming you're talking about operator functions and not operators as a syntactic unit of the language.
If you overload operators on any type, you may actually return whatever you want.
This also makes a lot of sense, because operations like * or () may sometimes very intuitively not return their input type. Imagine multiplying a complex number type with a real number type. Or an operator that returns an element from a collection.
You may also overload the ++ and -- operators to not return anything thus removing the extremely error-prone mixing of side-effect and expression value that the standard version has.
No.
Consider these two examples here:
int multiply (int a, int b) {
return a*b;
}
void multiply_void(int a, int b) {
cout << a*b;
//or cout << multiply(a,b);
}
First function will return an integer value which can be used by another function.
The value is returned and stored in memory to be used when necessary. If not, it is not visible to human & just sits happily in the memory.
Second function will output to the default output device(usually console).
The value returned by the multiplication operator is passed to an output device.
The function multiply_void does not return an actual value.
All operators return something. They are called operators because they operate something , therefore they will return something. Those Operators who do not return something cant be called operators. Either they will return some value or return True or False depending upon the situation.
Operators on their own do not necessarily return anything. Function calls return values. Operators can result in values.
Operators can sometimes invoke functions. Examples include:
• The function call operator.
• An overloaded operator that gets transformed into a function call.
• operator new, which is invoked as part of a new-expression, is a function call.
My only purpose in mentioning function calls is to clarify the result vs. return. Based on looking at all 126 instances of “return” and words derived from it in [expr], the section seems to carefully use the word return to refer to:
• the result of a function call
• aspects of control flow related to coroutines
OK, that’s enough pedantry on result vs. return.
C++ Operators return something or not is depends upon how you used them. Built-In C++ operators return some value until and unless enforced to return void.

operator= overloading return argument instead of *this

I have a theoretical question:
Usually, in an operator= implementation, it returns *this. But what happens if we instead returned *other, where other is the right hand side of the assignment?
Thanks
Reason for returning *this is to enable assignment in this form
a = b = c
this is same as
a.operator=( b.operator=(c))
If you return other, compiler wont be able to compile this kind of assignments.
The purpose of returning *this from the assignment operator is to allow for assignment chaining, e.g.
int x = 2;
int y = x = 5; // Equivalent to: 'int y = (x = 5);'
The copy assignment operator is usually declared as:
T& operator=(const T& other);
Here the argument other is declared as const and can thus not be returned. Returning by const T& will also act differently as the caller can not assign to the returned reference, thus disallowing assignment chaining.
Often you will also assign with a temporary. If you customize the behaviour of the assignment operator to return a reference to this temporary it can result in dangling references and other dangerous behaviours.
std::string s;
(s = "abc") = "def"; // Can't assign to rhs.
It is possible to customize the behaviour of the assignment operator to achieve different behaviours, but you should generally refrain from doing so to keep the meaning of the operator clear. If you want custom behaviour it's better to provide a function with a good descriptive name.
More info about operator overloading can be found here.
Well their will be two factual differences :
You will be forced to return a reference to a const instance from your operator= or a copy
You assignment will return a reference on its right hand side instead of its left hand side
Mostly what this means is you will surprise some developers that might exploit the return value of your assignment thinking it gives back the left hand side as most objects do.
But there is not so many people who sanely intend to write things like:
(a = b).do_action();
And since you just assigned the right value to the left one and you are returning either a const instance or a copy, most of the operations will most likely result in the same thing no matter if they are called on the left or right instance. So basically most of the time it won't change anything to your life.
However except if you are a boost::spirit developer, you are highly encouraged to avoid such things for the sanity of your colleagues :)

what is return type of assignment operator?

I am just starting C++. I am a bit confused about the return type of assignment and dereference operator. I am following the book C++ Primer. At various occasions, the author says that the return type of assignment operator is reference to the type of left hand operand but later on, he says that the return type is the type of the left hand operand. I have referred C++11 Standard Sec. 5.17, where the return type is described as "lvalue referring to left hand operand".
Similarly, I can't figure out whether dereference returns the pointed-to object or the reference to the object.
Are these statements equivalent? If so, then how? Any explanation would be appreciated.
The standard correctly defines the return type of an assignment operator.
Actually, the assignment operation itself doesn't depend on the return value - that's why the return type isn't straightforward to understanding.
The return type is important for chaining operations.
Consider the following construction: a = b = c;. This should be equal to a = (b = c), i.e. c should be assigned into b and b into a. Rewrite this as a.operator=(b.operator=(c)). In order for the assignment into a to work correctly the return type of b.operator=(c) must be reference to the inner assignment result (it will work with copy too but that's just an unnecessary overhead).
The dereference operator return type depends on your inner logic, define it in the way that suits your needs.
They can both be anything, but usually operator = returns the current object by reference, i.e.
A& A::operator = ( ... )
{
return *this;
}
And yes, "reference to the type of left hand operand" and "lvalue referring to left hand operand" mean the same thing.
The dereference operator can have basically any return type. It mostly depends on the logic of the program, because you're overloading the operator that applies to an object, not to a pointer to the object. Usually, this is used for smart pointers, or iterators, and return the object they wrap around:
struct smart_ptr
{
T* innerPtr;
T* smart_ptr::operator* ()
{
return innerPtr;
}
}
smart_ptr p;
T* x = *p;
I have seen similar issues, but I guess it would be best to use
X& X::operator=(const X&);
Using this, you will be able to reuse the object in a chain-assignment.

Why must the copy assignment operator return a reference/const reference?

In C++, the concept of returning reference from the copy assignment operator is unclear to me. Why can't the copy assignment operator return a copy of the new object? In addition, if I have class A, and the following:
A a1(param);
A a2 = a1;
A a3;
a3 = a2; //<--- this is the problematic line
The operator= is defined as follows:
A A::operator=(const A& a)
{
if (this == &a)
{
return *this;
}
param = a.param;
return *this;
}
Strictly speaking, the result of a copy assignment operator doesn't need to return a reference, though to mimic the default behavior the C++ compiler uses, it should return a non-const reference to the object that is assigned to (an implicitly generated copy assignment operator will return a non-const reference - C++03: 12.8/10). I've seen a fair bit of code that returns void from copy assignment overloads, and I can't recall when that caused a serious problem. Returning void will prevent users from 'assignment chaining' (a = b = c;), and will prevent using the result of an assignment in a test expression, for example. While that kind of code is by no means unheard of, I also don't think it's particularly common - especially for non-primitive types (unless the interface for a class intends for these kinds of tests, such as for iostreams).
I'm not recommending that you do this, just pointing out that it's permitted and that it doesn't seem to cause a whole lot of problems.
These other SO questions are related (probably not quite dupes) that have information/opinions that might be of interest to you.
Has anyone found the need to declare the return parameter of a copy assignment operator const?
Overloading assignment operator in C++
A bit of clarification as to why it's preferable to return by reference for operator= versus return by value --- as the chain a = b = c will work fine if a value is returned.
If you return a reference, minimal work is done. The values from one object are copied to another object.
However, if you return by value for operator=, you will call a constructor AND destructor EACH time that the assignment operator is called!!
So, given:
A& operator=(const A& rhs) { /* ... */ };
Then,
a = b = c; // calls assignment operator above twice. Nice and simple.
But,
A operator=(const A& rhs) { /* ... */ };
a = b = c; // calls assignment operator twice, calls copy constructor twice, calls destructor type to delete the temporary values! Very wasteful and nothing gained!
In sum, there is nothing gained by returning by value, but a lot to lose.
(Note: This isn't meant to address the advantages of having the assignment operator return an lvalue. Read the other posts for why that might be preferable)
When you overload operator=, you can write it to return whatever type you want. If you want to badly enough, you can overload X::operator= to return (for example) an instance of some completely different class Y or Z. This is generally highly inadvisable though.
In particular, you usually want to support chaining of operator= just like C does. For example:
int x, y, z;
x = y = z = 0;
That being the case, you usually want to return an lvalue or rvalue of the type being assigned to. That only leaves the question of whether to return a reference to X, a const reference to X, or an X (by value).
Returning a const reference to X is generally a poor idea. In particular, a const reference is allowed to bind to a temporary object. The lifetime of the temporary is extended to the lifetime of the reference to which it's bound--but not recursively to the lifetime of whatever that might be assigned to. This makes it easy to return a dangling reference--the const reference binds to a temporary object. That object's lifetime is extended to the lifetime of the reference (which ends at the end of the function). By the time the function returns, the lifetime of the reference and temporary have ended, so what's assigned is a dangling reference.
Of course, returning a non-const reference doesn't provide complete protection against this, but at least makes you work a little harder at it. You can still (for example) define some local, and return a reference to it (but most compilers can and will warn about this too).
Returning a value instead of a reference has both theoretical and practical problems. On the theoretical side, you have a basic disconnect between = normally means and what it means in this case. In particular, where assignment normally means "take this existing source and assign its value to this existing destination", it starts to mean something more like "take this existing source, create a copy of it, and assign that value to this existing destination."
From a practical viewpoint, especially before rvalue references were invented, that could have a significant impact on performance--creating an entire new object in the course of copying A to B was unexpected and often quite slow. If, for example, I had a small vector, and assigned it to a larger vector, I'd expect that to take, at most, time to copy elements of the small vector plus a (little) fixed overhead to adjust the size of the destination vector. If that instead involved two copies, one from source to temp, another from temp to destination, and (worse) a dynamic allocation for the temporary vector, my expectation about the complexity of the operation would be entirely destroyed. For a small vector, the time for the dynamic allocation could easily be many times higher than the time to copy the elements.
The only other option (added in C++11) would be to return an rvalue reference. This could easily lead to unexpected results--a chained assignment like a=b=c; could destroy the contents of b and/or c, which would be quite unexpected.
That leaves returning a normal reference (not a reference to const, nor an rvalue reference) as the only option that (reasonably) dependably produces what most people normally want.
It's partly because returning a reference to self is faster than returning by value, but in addition, it's to allow the original semantics that exist in primitive types.
operator= can be defined to return whatever you want. You need to be more specific as to what the problem actually is; I suspect that you have the copy constructor use operator= internally and that causes a stack overflow, as the copy constructor calls operator= which must use the copy constructor to return A by value ad infinitum.
There is no core language requirement on the result type of a user-defined operator=, but the standard library does have such a requirement:
C++98 §23.1/3:
” The type of objects stored in these components must meet the requirements of CopyConstructible
types (20.1.3), and the additional requirements of Assignable types.
C++98 §23.1/4:
” In Table 64, T is the type used to instantiate the container, t is a value of T, and u is a value of (possibly const) T.
Returning a copy by value would still support assignment chaining like a = b = c = 42;, because the assignment operator is right-associative, i.e. this is parsed as a = (b = (c = 42));. But returning a copy would prohibit meaningless constructions like (a = b) = 666;. For a small class returning a copy could conceivably be most efficient, while for a larger class returning by reference will generally be most efficient (and a copy, prohibitively inefficient).
Until I learned about the standard library requirement I used to let operator= return void, for efficiency and to avoid the absurdity of supporting side-effect based bad code.
With C++11 there is additionally the requirement of T& result type for default-ing the assignment operator, because
C++11 §8.4.2/1:
” A function that is explicitly defaulted shall […] have the same declared function type (except for possibly differing ref-qualifiers and except that in
the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T”, where T is the name of the member function’s class) as if it had been implicitly declared
I guess, because user defined object should behave like builtin types.
For example:
char c;
while ((c = getchar()) != -1 ) {/* do the stuff */}
Copy assignment should not be void, otherwise assignment chain will not work
a = b = c;
// because assignment operator is right-associative
// it is equal to
a = (b = c); // oops, (b = c) return nothing, the code won't compile
Copy assignment should not return a value, otherwise unnecessary copy constructor and destructor will be called
// suppose a, b and c are of type X, which holds some resource that will take efforts to copy
a = b = c;
// is equal to
X temp1.X::( (b = c) ); // copy constructor called once
X temp2.X::( a.X::operator=(temp1) ); // copy constructor called twice; temp1 destructed inside a.X::operator=(temp1)
Copy assignment should not return rvalue reference cos it may have the assigned object moved. Again take the assignment chain for example
a = b = c;
// if a has a copy assignment overload that takes rvalue reference as argument like the following
X& operator=(X &&);
// then the result of (b = c) will be moved into a, and make b an invalid object afterwards