Are temporary C++ objects lvalues? [duplicate] - c++

This question already has answers here:
Method call acting unexpectedly like an l-value
(4 answers)
Closed 4 years ago.
I'm puzzling over the following code, which (surprisingly, to me) compiles:
class A {
int a=0;
};
A returnsA(void)
{
static A myA;
return myA;
}
void works(void)
{
A anotherA;
returnsA() = anotherA;
}
I can't find anything in the standard or on the web which suggests that it shouldn't compile. It just seems very weird, to me.
I guess returnsA() is returning an object (a copy of myA), so we invoke the default copy assignment operator on it, anotherA gets copy-assigned to the returned object, which then goes out of scope and is destroyed.
I was expecting behavior more like this, which doesn't compile:
int returnsint(void)
{
static int i=0;
return i;
}
void doesntwork(void)
{
int anotherint=0;
returnsint() = anotherint;
}
Can anybody enlighten me further about this behavior?

Objects are not lvalues or rvalues. The words lvalue and rvalue are expression categories. They categorize expressions, not objects.
For the line:
returnsA() = anotherA;
since the operands of = are class types, an overloaded operator function is searched for. operator= is special in that if the user does not explicitly overload it, there is a default overload generated. That overload is a member function with signature:
A& A::operator=(A const&);
and the call returnsA() = anotherA; is transformed to returnsA().operator=(anotherA);, this is the basic mechanics of member operator overloading.
The expression returnsA() has category prvalue. However it is perfectly fine to call member functions on prvalue expressions, so there is no problem here.
If you want to dissuade your class from accepting this sort of assignment, see here. And for why the default assignment operator doesn't work that way, see here.

Related

What can be done to prevent misleading assigment to returned value?

After many years of using C++ I realized a quirk in the syntax when using custom classes.
Despite being the correct language behavior it allows to create very misleading interfaces.
Example here:
class complex_arg {
double r_;
double phi_;
public:
std::complex<double> value() const {return r_*exp(phi_*std::complex<double>{0, 1});}
};
int main() {
complex_arg ca;
ca.value() = std::complex<double>(1000., 0.); // accepted by the compiler !?
assert( ca.value() != std::complex<double>(1000., 0.) ); // what !?
}
https://godbolt.org/z/Y5Pcjsc8d
What can be done to the class definition to prevent this behavior?
(Or at the least flag the user of the clas that the 3rd line is not really doing any assignment.)
I see only one way out, but it requires modifying the class and it doesn't scale well (to large classes that can be moved).
const std::complex<double> value() const;
I also tried [[nodiscard]] value() but it didn't help.
As a last resort, maybe something can be done to the returned type std::complex<double> ? (that is, assuming one is in control of that class)
Note that I understand that sometimes one might need to do (optimized) assign to a newly obtained value and passe it to yet another function f( ca.value() = bla ). I am not questioning this usage per se (although it is quite confusing as well); I have the problem mostly with ca.value() = bla; as a standalone statement that doesn't do what it looks.
Ordinarily we can call a member function on an object regardless of whether that object's value category is an lvalue or rvalue.
What can be done to the class definition to prevent this behavior?
Prior to modern C++ there was no way prevent this usage. But since C++11 we can ref-qualify a member function to do what you ask as shown below.
From member functions:
During overload resolution, non-static member function with a cv-qualifier sequence of class X is treated as follows:
no ref-qualifier: the implicit object parameter has type lvalue reference to cv-qualified X and is additionally allowed to bind rvalue implied object argument
lvalue ref-qualifier: the implicit object parameter has type lvalue reference to cv-qualified X
rvalue ref-qualifier: the implicit object parameter has type rvalue reference to cv-qualified X
This allows us to do what you ask for a custom managed class. In particular, we can & qualify the copy assignment operator.
struct C
{
C(int)
{
std::cout<<"converting ctor called"<<std::endl;
}
C(){
std::cout<<"default ctor called"<<std::endl;
}
C(const C&)
{
std::cout<<"copy ctor called"<<std::endl;
}
//-------------------------v------>added this ref-qualifier
C& operator=(const C&) &
{
std::cout<<"copy assignment operator called"<<std::endl;;
return *this;
}
};
C func()
{
C temp;
return temp;
}
int main()
{
//---------v---------> won't work because assignment operator is & qualified
func() = 4;
}

const reference of a class initialization [duplicate]

This question already has answers here:
Why not non-const reference to temporary objects? [duplicate]
(4 answers)
Closed 6 years ago.
Lets say I have class A:
class A {
int i;
public:
A(){};
A(int i){this->i=i;};
};
And a simple test function:
void test(const A &a){...}
Now, if I do:
int main()
{
test(2);
}
It compiles and it will call the A(int i) constructor. But when I change the argument to be non-const: void test(A &a) I get a compilation error.
What is the difference between those cases, why the first one is allowed and the second not, and what actually happens in the initialization of the first case?
The difference between the two cases is that C++ compiler is allowed to create temporary objects to be passed to functions accepting a const references, but functions accepting non-const references must be provided with actual objects.
When you call test(2) what actually happens is this:
A hidden(2);
test(hidden);
The compiler creates hidden object, initializes it with 2, and passes the result to test. Since test is guaranteed to not modify A, this is fine.
When test does not provide such guarantee: imagine test that sets a new value:
void test(A& a) {
a.i++; // let's pretend "i" is public
}
If you call test with an actual object, i.e. A a(3); test(a); you can harvest the result of the update from a after test return. Calling test(2), on the other hand, gives you no way to access the result of an update. This indicates a potential error in the logic, so the compiler treats it as an error.
This is an interesting case. using const you can bind a reference to an rvalue. A more simpler example is below.
int get_num()
{
return 2;
}
int main(){
int& p = get_num(); // This is a compiler error. Can't create a non-const reference to an rvalue
const int& q = get_num(); // this will work. Can create const reference to an rvalue
}
This is part of the c++ standard.
what actually happens in the initialization of the first case?
In the 1st case, a temporary A will be constructed implicitly by A::A(int) and then bound to the lvalue reference to const. The effect of test(2); is same as test(A(2));.
Temporary can't be bound to lvalue reference to non-const, that's why the 2nd case failed.

Why doesn't std::reference_wrapper implicitly cast to a reference when calling member function? [duplicate]

This question already has answers here:
How to correctly use std::reference_wrappers
(2 answers)
Closed 4 years ago.
I don't understand exactly why one cannot use a std::reference_wrapper like this:
#include <vector>
#include <functional>
struct Foo
{
void f() {};
};
int main()
{
std::vector<std::reference_wrapper<Foo>> vrFoo;
Foo foo;
vrFoo.push_back(foo);
// vrFoo[0].f(); // error
vrFoo[0].get().f(); // or static_cast<Foo&>(v[0]).f();
}
Why do we have to use the get() member function? It looks like std::reference_wrapper has an implicit conversion to T& via operator T&() const noexcept, see http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
so why isn't v[0] implicitly converted to a reference?
In other situations, such as
std::cout << v[0] << std::endl
this conversion takes place (I assume here that Foo overloads operator<<)
Because . is always used to access members of the object it's applied to. Type conversions aren't considered.
There's a proposal to allow overloading of operator., to enable exactly what you want, but that won't be standard until at least C++17, if at all.
This is a language limitation as #MikeSeymour pointed out.
And this is the reason why I think std::reference_wrapper should have an overloaded operator& that returns the address of the wrapped object:
template<class T>
struct reference_wrapper{
...
T* operator &(){return ptr;}
}
So that later one can use &Foo->f() or (*&Foo).f() instead of Foo.get().f() or static_cast<T&>(Foo).f().

Prevent assigning to rvalue [duplicate]

This question already has an answer here:
Operator overload which permits capturing with rvalue but not assigning to
(1 answer)
Closed 8 years ago.
Say I have a class with an overridden assignment operator:
class Test
{
public:
Test& operator =(const Test&) {return *this;}
};
Test f();
Is there any way to make it a compile-time error to assign to the result of a function?
Examples:
f() = test();
Test t;
f() = t;
// if there were also a Test Test::operator+(Test);
(t + t) = test();
This already happens for primitive types, and I want to replicate it for this class:
int g();
g() = 5; // error
(3 + 4) = 5; // error
Edit: I'm using Visual C++, which doesn't support all of C++11. Specifically, it doesn't support ref-qualifiers.
If you don't want your operator=() to be invoked on temporaries (rvalues), then qualify it lvalue as:
Test& operator =(const Test&) & {return *this;}
//^^^ note this
Now the following
f() = t;
will give compilation error.
In C++11, you can ref-qualify your assignment operator so that it can only be called on lvalue references. However, I can't find anything definite in the standard about whether such an operator is still the copy assignment operator.
In C++03, people came up with the idea of only returning const objects from functions, but that's a bad idea because it prevents moving in C++11.
In the end, it's probably not worth going to any effort to do this.

Should I still return const objects in C++11? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Should I return const objects?
(The original title of that question was: int foo() or const int foo()? explaining why I missed it.)
Effective C++, Item 3: Use const whenever possible. In particular, returning const objects is promoted to avoid unintended assignment like if (a*b = c) {. I find it a little paranoid, nevertheless I have been following this advice.
It seems to me that returning const objects can degrade performance in C++11.
#include <iostream>
using namespace std;
class C {
public:
C() : v(nullptr) { }
C& operator=(const C& other) {
cout << "copy" << endl;
// copy contents of v[]
return *this;
}
C& operator=(C&& other) {
cout << "move" << endl;
v = other.v, other.v = nullptr;
return *this;
}
private:
int* v;
};
const C const_is_returned() { return C(); }
C nonconst_is_returned() { return C(); }
int main(int argc, char* argv[]) {
C c;
c = const_is_returned();
c = nonconst_is_returned();
return 0;
}
This prints:
copy
move
Do I implement the move assignment correctly? Or I simply shouldn't return const objects anymore in C++11?
Returning const objects is a workaround that might cause other problems. Since C++11, there is a better solution for the assignment issue: Reference Qualifiers for member functions. I try to explain it with some code:
int foo(); // function declaration
foo() = 42; // ERROR
The assignment in the second line results in a compile-time error for the builtin type int in both C and C++. Same for other builtin types. That's because the assignment operator for builtin types requires a non-const lvalue-reference on the left hand side. To put it in code, the assignment operator might look as follows (invalid code):
int& operator=(int& lhs, const int& rhs);
It was always possible in C++ to restrict parameters to lvalue references. However, that wasn't possible until C++11 for the implicit first parameter of member functions (*this).
That changed with C++11: Similar to const qualifiers for member functions, there are now reference qualifiers for member functions. The following code shows the usage on the copy and move operators (note the & after the parameter list):
struct Int
{
Int(const Int& rhs) = default;
Int(Int&& rhs) noexcept = default;
~Int() noexcept = default;
auto operator=(const Int& rhs) & -> Int& = default;
auto operator=(Int&& rhs) & noexcept -> Int& = default;
};
With this class declaration, the assignment expression in the following code fragment is invalid, whereas assigning to a local variable works - as it was in the first example.
Int bar();
Int baz();
bar() = baz(); // ERROR: no viable overloaded '='
So there is no need to return const objects. You can restrict the assigment operators to lvalue references, so that everything else still works as expected - in particular move operations.
See also:
What is "rvalue reference for *this"?
Returning a const object by value arguably was never a very good idea, even before C++11. The only effect it has is that it prevents the caller from calling non-const functions on the returned object – but that is not very relevant given that the caller received a copy of the object anyway.
While it is true that being returned a constant object prevents the caller from using it wrongly (e.g. mistakenly making an assignment instead of a comparison), it shouldn't be the responsibility of a function to decide how the caller can use the object returned (unless the returned object is a reference or pointer to structures the function owns). The function implementer cannot possibly know whether the object returned will be used in a comparison or for something else.
You are also right that in C++11 the problem is even graver, as returning a const effectively prevents move operations. (It does not prevent copy/move elision, though.)
Of course it is equally important to point out that const is still extremely useful (and using it no sign of paranoia) when the function returns a reference or a pointer.
The reason that your call to const_is_returned triggers copy constructor rather than move constructor is the fact that move must modify the object thus it can't be used on const objects. I tend to say that using const isn't advised in any case and should be subjected to a programmer judgement, otherwise you get the things you demonstrated. Good question.