How does constant reference makes a difference? - c++

I dont understand why the following code doesn't work:
#include <iostream>
using namespace std;
class PayOff{};
class PayOffCall : public PayOff{};
class PayOffBridge{
public:
PayOffBridge( PayOff& innerPayOff){};
};
class VanilaOption{
public:
VanilaOption(PayOffBridge& ThePayOff_){cout << " test " <<endl;}
};
int main () {
PayOffCall thePayOff;
VanilaOption theOption(thePayOff);
return 0;
}
For some reason changing the reference to const in VanilaOption class (code below) makes it work, can someone explain how does it work? The error I get is: no matching function for call to 'VanilaOption::VanilaOption(PayOffCall&)'
but it doesn't help me to figure it out.
class VanilaOption{
public:
VanilaOption(const PayOffBridge& ThePayOff_){cout << " test " <<endl;}
};
I also don't understand why passing PayOffCall reference when PayOffBridge reference is expected works, can someone help me out on this?
Thank you!

The original code doesn't work because thePayOff isn't a value of type PayOffBridge, but rather of type PayOffCall.
The modified code works because it allows the construction of a temporary PayOffBridge object from the PayOff subobject of the PayOffCall object, and then construct the VanillaOption object from that temporary. That is because:
the PayOffBridge constructor is non-explicit,
an lvalue of a derived class can be converted to an lvalue of any of its accessible base classes, and
because temporaries can bind to const lvalue references (but not to non-const lvalue references).
In other words, the const reference version allows code like:
VanilaOption theOption(PayOffBridge(thePayOff));
// ^^^^^^^^^^^^^^^^^^^^^^^-----< temporary
Whereas for the non-const version you'd need a mutable lvalue, perhaps like:
PayOffBridge b(thePayOff);
VanilaOption theOption(b);
// ^----- lvalue

A PayoffCall (like thePayoff variable) is not a PayOffBridge, so it can't be bound to a reference to PayOffBridge.
However, it can be converted to PayOffBridge, via the converting PayOffBridge constructor.
With a reference to const argument such a conversion is performed, producing a temporary that's bound to the formal argument.
A reference to const can in general be bound to a temporary.
An ordinary reference to non-const can't be bound to a temporary.

Related

Implicit call of Class constructor from Initialisation of another class

The following code-snippet seemingly works:
#include <iostream>
class Filling{
public:
const int num;
Filling(int num_)
: num(num_)
{ }
};
class Nougat{
public:
Nougat(const Filling& filling)
: reference(filling)
{ }
const Filling& get_filling() const{ return reference; }
private:
const Filling& reference;
};
class Wrapper{
public:
Wrapper(const Nougat& nougat)
: reference(nougat)
{ }
const Nougat& get_nougat() const{ return reference; };
private:
const Nougat& reference;
};
int main(){
Filling filling(3);
Wrapper wrap(filling); /*(0)*/
std::cout << wrap.get_nougat().get_filling().num << std::endl;
}
Although the initialisation is improper (at /*(0)*/), because the Wrapper class should accept a Nougat class, but instead it is given a Filling class.
Since the constructor takes references, I believe the compiler is creating a temporary Nougat class from the constructor argument, and then passes that to initialise the Wrapper class.
Getting a reference from a temporary results in undefined behaviour.
Is this what is really happening?
If so, how can this code compile?
Wrapper's constructor expects a reference to Nougat, but you are giving it a Filling. As with all function calls in C++, in such a situation implicit conversion sequences for all arguments are considered.
An implicit conversion sequence could be for example a cast from a derived class reference to a base class reference, but since Nougat and Filling are not related in that way, this doesn't apply here.
Another allowed step in an implicit conversion sequence are user-defined conversions. This also includes so-called converting constructors of the target type, that is a constructor not marked explicit. This is the case for the constructor Nougat(const Filling& filling), which allows for conversion from Filling to Nougat.
So, this constructor may be called to construct a temporary object of Nougat type as an implicit conversion. The resulting temporary can bind to a const lvalue reference as well.
To avoid such unintentional conversions, you should mark constructors as explicit, at least if they take exactly one argument, except if you really intent for such implicit conversions to happen.
By the end of the construction of wrapper, the temporary object is destroyed and therefore the reference obtained in
wrap.get_nougat()
is dangling, and it's following use causes undefined behavior.

Mixing Rvalue and LValue reference

I'm trying to better understand LValue, RValue, and how std::move works.
I have the following code
#include <string>
class A
{
public:
A() = default;
A(std::string&& aString): myString(std::move(aString)) {}
std::string myString;
};
class B
{
public:
void InitMembers(std::string& aString) { myA = A(std::move(aString));}
private:
A myA;
};
int main()
{
B b;
std::string hello;
b.InitMembers(hello);
}
my questions are:
In void InitMembers(string& aString) { myA = A(std::move(aString));} I understand that I have to use std::move to aString in order to cast aString from an LValue reference to a RValue reference. But I have some doubts regarding the meaning of aString in the InitMember scope. aString is provided as an LValue reference, but in the method scope it's considered as an LValue and that's why I have to use the std::move? Std::move should rely on reference deduction (right?), how does it deduce the type in this case? It will deduce a type "string" o "string&" since aString is provided as a LValue Reference in the method's arguments?
Why do I have to use std::move also in the constructor initializer of A? Shouldn't be enough the fact that aString is an RValue reference, which triggers the move constructor?
Isn't the following implementation good as the one above?
#include <string>
class A
{
public:
A() = default;
A(std::string& aString): myString(std::move(aString)) {}
std::string myString;
};
class B
{
public:
void InitMembers(std::string& aString) { myA = A(aString);}
private:
A myA;
};
int main()
{
B b;
std::string hello;
b.InitMembers(hello);
}
Thanks :)
Concerning
A(std::string&& aString): myString(std::move(aString)) {}
std::string&& denotes an rvalue reference to a std::string. Rvalue references only bind to rvalues (both prvalues and xvalues), so the two possible call sites will be like this:
// assuming this is defined somewhere
std::string f(void); // returns by value, i.e. `f()` is an rvalue
// call site 1
A a1{f()}; // `f()` is an rvalue (precisely a prvalue)
// assuming this
std::string s{"ciao"}; // s is an lvalue
// call site 2
A a2{std::move(s)}; // `std::move(s)` is an rvalue (precisely an xvalue)
// i.e. with `std::move` we turn s into an rvalue argument,
// so it can bind to the rvalue reference parameter
// don't expect s to be the one it was before constructing a2
In either case, what does the constructor do with aString?
Well, aString is an lvalue, because "it has a name" (the actual definition is a bit more complicated, but this is the easiest one to get started with, and it isn't all that wrong after all), so if you use it verbatim, the compiler won't assume it is bound to a temporary and it won't let myString steal it resources.
But you know that aString is bound to a temporary, because you've declared it as std::string&&, so you pass it as std::move(aString) to tell the compiler "treat this is a temporary".
Yes, technically also the compiler knows that aString is bound to a temporary, but it can't std::move it automatically. Why? Because you might want to use it more than once:
A(std::string&& aString) : myString(aString/* can't move this */) {
std::cout << aString << std::endl; // if I want to use it here
}
// yes, this is a very silly example, sorry
As regards
void InitMembers(std::string& aString) { myA = A(std::move(aString));}
aString denotes an lvalue reference to non-const std::string, so you can pass to .InitMembers only non-const lvalues.
Then inside the function you're std::moveing it to tell A's constructor "look, this is a temporary". But that also means that at the call site (b.InitMembers(hello);) you're leaving the input (hello) in a moved-from state, just like the s in the first example above. That's ok, because the caller knows that InitMembers takes its parameter by non-const lvalue reference, so it is aware that the argument they pass can be changed by the call. Just like in the previous example it's the user who's writing std::move around s, so they're supposed to know what they do.
For more details about how std::move works (and std::forward as well), I want to point you to this answer of mine.

How to initialize an object reference in C++?

I tried to do
MyClass& x;
x = MyClass(a,b,c);
But C++ won't let me do so because it thinks that x is uninitialized at the beginning.
So I tried to do
MyClass& x = MyClass(a,b,c);
But got error saying invalid initialization of non-const reference of type 'MyClass&' from an rvalue of type 'MyClass'
What's wrong with it? It seems that I simply can't do anything now. How do I get around the initialization issue?
An ordinary reference to non-const must be initialized with an lvalue expression (essentially an expression that refers to a memory location), e.g.
MyClass o{ a, b, c };
MyClass& r = o;
If the reference is to const, or if it is an rvalue reference (denoted by &&), then the initializer can instead be an rvalue expression, such as a temporary produced by a function invocation:
MyClass const& rc = foo();
MyClass&& rr = foo();
In these cases, for a local reference the lifetime of the temporary is extended to the scope of the reference.
And one special feature is that if the initializer produces a temporary of a derived class, it's that full derived class object whose lifetime is extended, i.e. there's no slicing to the class specified for the reference.
More generally the reference can be bound to any part of a temporary, as long as that part has a compatible type, and this will extend the lifetime of the full temporary object.
A reference must refer to an already-existing object. So you need to have an object first before you can refer to it.
MyClass y = MyClass(a,b,c);
MyClass &x = y;

c++ strage solution to compilation error

Take a look at the following code:
struct s
{
s& operator+() {return*this;}
};
void foo(s &) {}
s bar() {}
int main()
{
foo(bar()); //comp error
foo(+bar()); //ok
}
I think the two lines in main() should be equivalent, because the compiler optimizes away operator+(), right? What sense does it make to accept one but not the other?
The line foo(bar()) is trying to bind an rvalue of type s (the temporary returned by bar()) to a non-const lvalue reference to s (the argument of foo()). This is illegal, hence the compilation error. rvalues can only be bound to rvalue references or to const lvalue references.
The expression +bar(), on the other hand, returns an lvalue reference (that's the return type of operator + ()), which can be bound to the lvalue reference parameter of foo().
Beware though: you are returning an lvalue reference to a temporary here. While using it inside foo() is still safe (the temporary will be destroyed when the full-expression in which it is created is completely evaluated, so after foo() returns), you would get Undefined Behavior if you stored that reference somewhere and dereferenced it later.
Therefore, instead of regarding this as a "solution to a compilation error", you should consider it a way to blindfold the compiler so you are free to sneak into big troubles.

Reference Member Required to be Const?

In this simple example, why do I need to make 'member' const in order to get this to compile?
struct ClassA
{
ClassA(int integer) {}
};
struct ClassB
{
ClassB(int integer):
member(integer)
{
}
const ClassA& member;
};
int main()
{
ClassB* b = new ClassB(12);
return 0;
}
Otherwise, I get this error:
error: invalid initialization of
reference of type 'ClassA&' from
expression of type 'int'
The reason why is that what's actually happening here is you're using an implicit conversion from int to ClassA in the initialization of member. In expanded form it is actually doing the following
member(ClassA(integer))
This means that the ClassA instance is a temporary. It's not legal to have a reference to a temporary variable only a const reference hence you get the compiler error.
The easiest fix is to remove the & modifier on member and make it just of type ClassA
ClassA member;
Additionally it's a good practice to put explicit in front of the ClassA constructor to avoid the silent implicit conversion.
explicit ClassA(int integer){}
Because you are trying to store a reference to a temporary object, and you may only store constant references to temporaries.
When you try to initialize the member reference of type ClassA& with the integer parameter of type int, the implicit conversion constructor ClassA::ClassA(int integer) is inovked to produce an unnamed temporary object of type ClassA from the integer variable. That unnamed temporary object is then used to initialize the reference member, creating a reference-to-temporary, which must be const.
I question your design here. If you're trying to initialize member with data passed by value to the ClassB constructor, having member be a reference is probably not the right thing to do. Why do you think member ought to be a reference and not just an object?
ClassA is a reference member of ClassB, so it must be instantiated with an instance of ClassA.
You're create a temporary, and initializing the reference from that temporary. Just like usual, to bind to a temporary, a reference has to be const.
I'd have serious second thoughts about this. A class with a reference member is rarely useful. When it is useful, you generally want to start with some existing object and pass a reference to that object through to the member reference. When you do this, you need to be sure the referenced object exists for the entire lifetime of the object containing a reference, so the two are quite tightly coupled -- which you'd usually rather avoid.