How to use operator= with a reference - c++

The code bellow pertains to question Error seemingly inconsistent behaviour in overloaded operator= used in variable definition that has been answered.
My question arized in the context of trying to define and initialize a reference to a structure using an integer.
So I need instead of writting "S s1=3;" I need to write "S& s1=3;" how am I going to manage it;
struct S{
int a,b;
void operator=(int x){a=x;b=x*x;}
S(){};
S(int x){a=x;b=x*x;}
};
int main(){
S s1;s1=5;
S s2;s2=7;
S s3=9;
S s4;
}
The code of main() should be modifified as follows:
int main(){
//S s0=S{15,20};
S s1;s1=5;
S s2;s2=7;
S s3=9;
S s4;
S& s5;s5=10;
S& s6=10;
}
but compiling I have errors:
main.cpp:16:5: error: ‘s5’ declared as reference but not initialized
main.cpp:17:8: error: invalid initialization of non-const reference of type ‘S&’ from an rvalue of type ‘int’ ( regards s6 ).

First of all, you are not using operator=, rather this is a copy-initialization.
That is, the expression S s1 = 3; uses non-explicit constructor S(int x) and also non-explicit and accessible copy constructor S(const S& x) (hopefully without any additional overhead).
Going further, you can't use S& s1 = 3; not because you cannot assign 3 to reference, but because the right hand side of assignment is an R-VALUE (that is, a temporary). However, you can extend its lifetime using const reference to l-value, as r-values like very much to be bound by const l-value references:
const S& s1 = 3;
This will work as long as S(int x) is not marked as explicit.
Alternatively, (you should not do that at all), you can use r-value reference:
S&& s1 = 3; // implicitly
S&& s2 = S(3); // explicitly
And the error you see trying to compile S& s5;:
main.cpp:16:5: error: ‘s5’ declared as reference but not initialized
tells you that you cannot just create a variable that is a reference (as opposed to pointers), without initializing it.
But if you had a validly initialized reference, then whenever you would use assignment, only then the operator= would be invoked:
S s1(1);
S& s1ref = s1;
S s2(2);
s1ref = s2; // s1.operator=(s2);

This is not possible; a reference must refer to an object. You can do either of the following (using C++98 syntax):
S const &s1 (3); // same as S const &s1 = S(3);
or
S s0(3); S &s1(s0);

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;
}

Behavior of function overloading with const

I'm working on g++ and here I have tried to overload a function by just adding const to parameter. It works fine and when it runs, it calls the function without const
Is this behavior specified in the C++ standard?
What the reason it calls the function without const
void print(const std::string& str){std::cout << "const" << str << std::endl;}
void print(std::string& str){std::cout << str << std::endl;}
int main()
{
std::string temp = "hello";
print(temp);
return 0;
}
Reference bindings are an identity category §13.3.3.1.4) but since the latter is more cv-qualified, for §13.3.3.2, the non-const is preferred (sample code from the standard):
int f(const int &);
int f(int &);
int i;
int j = f(i); // calls f(int &)
That is standard behavior. Any other behavior would lead to crazy behavior. In particular, the non-const function would not be callable at all.
const is part of method signature. Overriding works only for methods with the same signature.
This behavior was made to avoid reverse situation when you use const method of base class to call not const method of child class.
The reason is this section in [over.ics.rank]/3 where this case is explicitly covered:
Standard conversion sequence S1 is a better conversion sequence than
standard conversion sequence S2 if […] — S1 and S2 are reference
bindings (8.5.3), and the types to which the references refer are the
same type except for top-level cv-qualifiers, and the type to which
the reference initialized by S2 refers is more cv-qualified than the
type to which the reference initialized by S1 refers.
S1 corresponds to the second overload and S2 to the first.
What the reason it calls the function without const
You always try to select the most specialized thing. That is the case in overload resolution just as it is in partial ordering of function templates. The second overload is more specialized than the first because the first can be called with arguments which the second cannot be called with - that is the basic reasoning behind this rule.
Overloading works by matching the types of the arguments, including the qualifiers. In your case temp has type std::string not const std::string. You have only initialised it with a literal constant, it is not itself constant.
Consider the following:
std::string temp( "hello" ) ;
print(temp); // hello
print( std::string("hello") ) ; // consthello
print( "hello" ) ; // consthello
print( static_cast<const std::string>(temp) ) ; // consthello
const std::string temp2( "hello" ) ;
print(temp2); // consthello
If you were to remove the non-const version, all three will call the remaining const overload. In this example, only the const version is in fact necessary (and preferred) since neither version modify the string object.
If on the other hand you removed the non-const version, there would be no function matching any but the first example above, and the build would fail. That is to say a non-const object can safely be passed as a const argument, but a const object cannot be passed as a non-const argument, because the function is not "promising" not to modify the object. You can force a const into a non-const argument by a const_cast as in:
const std::string temp2("hello") ;
print( const_cast<std::string&>(temp2) ) ; // hello
But if print() were to attempt to modify the object in this case the results are undefined, so consider the practice unsafe.
Making an argument const indicates intent, allows the compiler to issue a diagnostic if the code is attempts to modify the object or pass it via a non-const argument to some other function. It may also potentially provide the compiler with optimisation possibilities.
Because calling the function taking std::string const& requires two implicit conversions: one to std::string const, one to std::string const&; whereas calling the function taking std::string& requires merely one implicit conversion (to std::string&), so that one is preferred.

Why does initializing non-const reference by rvalue work (in C++11)?

The following example code compiles.
#define USE_RVALUE // line 1
template<class data_type>
class Container
{
data_type& data;
public:
#ifdef USE_RVALUE
Container(data_type&& _data) : data(_data) {}
#endif
Container(data_type& _data) : data(_data) {}
};
int main()
{
double d = 42.0;
Container<double> c1(d);
Container<double> c2(1.0f); // line 18
return 0;
}
My compiler command:
g++ -std=c++11 -Wall ref.cpp -o ref # g++ is 4.7.1
If we outcomment line 1, g++ complains:
no matching function for call to ‘Container<double>::Container(float)’
ref.cpp:18:34: note: candidates are:
ref.cpp:11:9: note: Container<data_type>::Container(data_type&) [with data_type = double]
ref.cpp:11:9: note: no known conversion for argument 1 from ‘float’ to ‘double&’
[... (more candidates)]
Of course, the error is clear, and a typical error in C++03: References from rvalues are not allowed, if these rvalues are not const. But why does it work with the rvalue constructor (i.e. the #ifdef enabled)? We have the same situation in the initializer list: Reference from non-const value.
Also, if you explain it... Is this code "good coding style" or "to avoid"?
The answer is simple enough: whatever has name - it is an lvalue. So in your ctor, with rvalue reference, _data is an lvalue in the body of ctor despite the fact it is an rvalue in the outer world. So at the end you have a dangling reference so you should not do such a thing.
Basically, when you accept something by &&(except when in template, aka Universal references) you should treat it as "this value are going to be destroyed". So you should steal from it, change it, whatever(except leaving it in some invalid state preventing its normal destruction). But you should never store pointer or reference to it. Because this object maybe destroyed right after your function complete. It is exactly the case you have. double(1.0f) exists in your ctor body only and has no meaning outside it and you still store reference to it.
It's because named r-values are not r-values.
Look at this:
double& ref = 5; // Error
const double& cref = 5; // Ok
double&& ref2 = 5; // Ok
double& ref3 = ref2; // Ok. ref2 is not r-value.
Assigning ref2 to ref3 is ok because ref2 is a named r-value.
In the same way, in your ctor _data is not an r-value, but an l-value.
Container(data_type&& _data) : data(_data) {}
When declaring parameters as taking an r-value you make it a requirement to pass an r-value to the function, but the argument itself becomes a named reference and is thus converted into an l-value. If you want to pass it to another function as an r-value you have to std::move it.

Value parameters to operator= cause strange compilation error

I'm trying to relearn C++, and I tend to want to have an intricate understanding of how everything works (not just "how do I do this"). So I'm wondering why this produces the error it does. Yes, I know that an overloaded assignment operator is supposed to use references (and it works fine if I do), but I'm hoping that an answer to this question might help me learn more about the language rules.
class some_class {
public:
int n1;
some_class(int z) : n1(z) { }
some_class(some_class &x) : n1(x.n1) { }
some_class operator= (some_class x) { n1 = x.n1; return *this; }
// some_class & operator= (some_class & x) { n1 = x.n1; return *this; } works fine
};
main () {
some_class a(10);
some_class b(20);
some_class c(30);
c = b = a; // error here
}
The compiler (C++03) gives me this, on the c = b = a line:
In function 'int main()':
error: no matching function for call to 'some_class::some_class(some_class)'
note: candidates are: some_class::some_class(some_class&)
note: some_class::some_class(int)
error: initializing argument 1 of 'some_class some_class::operator=(some_class)'
It's confusing to me, because b = a works fine, and it's looking for a constructor that I'm not legally allowed to declare. I realize that in c = b = a, the b = a part returns a value (not a reference), and that may result in the result being copied to a temporary. But why would c = <temporary> result in a compilation error when b = a wouldn't? Any idea what's going on?
Your copy constructor has a non-const reference as its parameter. Temporaries can't be bound to non-const references. When you do:
c = b = a;
This is equivalent (as you say) to:
c.operator=(<temporary>);
It therefore tries to invoke your copy constructor with a temporary whilst initialising the first argument of the call to operator=. This fails for the reason mentioned. A sensible way to fix it is to change the signature of operator= to the more conventional:
some_class& operator=(const some_class& x);
The copy constructor would not then be needed in the implementation of operator=, since the argument to operator= would not be copied. However, copy constructors should generally take a const reference parameter, so you should also change the signature of the copy constructor to:
some_class(const some_class& x);
What is causing the error is your copy constructor, which should be
some_class(const some_class&)
This is because you cannot pass a temporary object to a non-const reference, which is what happens in your chained assignment. This is because your assignment operator returns by value which creates a temporary object, which is then passed to the next assignment operator as a value parameter. This invokes the copy constructor, which has a non-const reference parameter and so cannot bind to the temporary object.
Assignment operator should be
some_class& operator= (some_class x)
or
some_class& operator= (const some_class& x)
That is, it takes a value or const reference paramater of the same type and returns a non-const reference, which is *this. Alternatively it can have void return type to prevent chaining.
I know other variations are "allowed", but don't use them unless you know what you are doing.
Unless you have a reason for a value parameter (copy-and-swap idiom for example), you should use const reference to prevent an extra copy.
some_class(some_class &x) : n1(x.n1) { }
some_class operator= (some_class x) { n1 = x.n1; return *this; }
should be
some_class(const some_class &x) : n1(x.n1) { }
some_class& operator=(const some_class& x) { n1 = x.n1; return *this; }
Using your original signatures, you can't copy or assign from const temporary objects as you show in your sample.
The key issue is also with unnecessary copy of data if we are not working with references.
There is one more way to solve this, given that you are trying to relearn c++, it would be interesting to know. C++11 have introduced r-value references and std::move to allow working with temporaries in copy constructor and assignment operators to reduce memory copies.
Move copy constructor and assignment operator would look like
some_class(some_class &&x) : n1(std::move(x.n1)) {}
some_class& operator=(some_class&& x) { n1 = std::move(x.n1); return *this; }
In the statement:
c = b = a;
As assignment operator has Right-to-Left associativity, b = a, is executed first. Since assignment operator returns value, copy constructor is invoked. Since temporary objects cannot be referred to by non-const references (as Stuart Golodetz already mentioned above), the compiler looks for some_class::some_class(some_class x) kind of constructor; and is complaining on not finding it.
The compilation succeeds only if you modify the signature of the copy constructor to:
some_class(const some_class& x);
Assignment operator, as others mentioned, returns a reference traditionally, for the reason that we could chain several of them. It is however not mandatory to design them so.

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.