Implicit call of Class constructor from Initialisation of another class - c++

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.

Related

Cast to reference type

Chapter 4.11.3 of the book C++ Primer says the following:
A named cast has the following form: cast-name<type>(expression);
where type is the target type of the conversion, and expression is the value to be cast. If type is a reference, then the result is an lvalue.
Can anyone give an example of this? Is conversion to a reference even possible?
Is conversion to a reference even possible?
Yes, depending on the object you begin with.
Most simple example is one lvalue reference converted to another. This can even be an added cv-qualifier (or removed, with const_cast), such as:
int const & foo(int & i) {
return static_cast<int const &>(i);
}
the derived-to-base casts and base-to-derived (dynamic) casts mentioned in the other answers are other such examples.
A more interesting example is the std::reference_wrapper, which is an object you can receive as an rvalue, and cast it to an lvalue reference of the contained type:
int & foo(std::reference_wrapper<int> i) {
return static_cast<int &>(i);
}
Note that the cast above happens implicitly (I could've used return i), but in some contexts (e.g. capturing variables with auto type) you might want to write the cast explicitly.
What is meant in C++ Primer is simply that the behavior of casts in these examples, and others, is basically what you would expect - the result of a cast to a reference type is an lvalue.
Here is a short example with a class hierarchy. In main you find code that constructs a Derived object on the heap and stores the pointer to it in p. If you want to work with pointers, you can just dynamic_cast<Derived*> that pointer. But sometimes like in function work you have a reference to the base class. If inside the function you know at some place that the object b is of type Derived (maybe you checked an enum member giving you the type or based on some other information you know that it must be some special type), then can directly cast from reference Base& to reference Derived&. In that special case of dynamic_cast it also makes a difference because casting a pointer will return nullptr if the cast is not possible, but casting a reference will throw an exception if the cast is not possible.
#include <memory>
struct Base {
virtual ~Base() = default;
};
struct Derived : Base {
void f() const noexcept {}
};
void work(const Base& b) noexcept
{
// Check what to do, then assume it must be a Derived, then work on it:
dynamic_cast<const Derived&>(b).f();
}
int main() {
std::unique_ptr<Base> p{ std::make_unique<Derived>() };
dynamic_cast<Derived*>(p.get())->f();
work(*p);
return 0;
}
Example when type is not a reference: If i is a variable of type int, then static_cast<long int>(i) returns a non-lvalue of type long int.
Example when type is a reference: static_cast<Base&>(objectOfDerivedType) returns an lvalue of type reference-to-Base.

Initialize rvalue reference member

I am trying to initialize a rvalue reference member in the following situations: struct A is a aggregate and class B has a user defined constructor so it is not an aggregate. According to cppreference here,
struct A {
int&& r;
};
A a1{7}; // OK, lifetime is extended
A a2(7); // well-formed, but dangling reference
The reference in my struct A should be correctly initialized and the temporary string should be extended, but it's not.
For my class B, in the same cppreference page:
a temporary bound to a reference member in a constructor initializer list persists only until the constructor exits, not as long as the object exists. (note: such initialization is ill-formed as of DR 1696).
(until C++14)
But I am still getting problem with MSVC with /std:c++latest. Am I missing anything?
struct A
{
std::string&& ref;
};
class B
{
std::string&& ref;
public:
B(std::string&& rref):ref(std::move(rref)){}
void print() {std::cout<<ref<<'\n';}
};
int main()
{
A a{ std::string{"hello world"} };
std::cout<<a.ref<<'\n'; //garbage in MSVC with c++latest, correct in GCC9.2
B b{ std::string{"hello world"} };
b.print(); //same
}
EDIT: Please tell me if I am getting dangling reference in these two cases in the first place. And for MSVC, the version is the latest update, v19.24.28315
EDIT2: OK I am actually confused by cppreference's explanation. I am not asking specificly for C++20. Please tell me under which C++ version, the code is well-formed or not.
EDIT3: Is there a proper way to initialize a rvalue reference member of a non-aggregate class? Since bind it to a temporary in a member initializer list is ill-formed (until C++14, does it mean it is good since C++14?) and passing a temporary to a constructor expecting an rvalue cannot extend its lifetime twice.
Your class A is an aggregate type, but B isn't, because it has a user-provided constructor and a private non-static member.
Therefore A a{ std::string{"hello world"} }; is aggregate initialization which does extend the lifetime of the temporary through reference binding to that of a.
On the other hand B is not aggregate initialization. It calls the user-provided constructor, which passes on the reference. Passing on references does not extend the lifetime of the temporary. The std::string object will be destroyed when the constructor of B exits.
Therefore the later use of a has well-defined behavior, while that of b will have undefined behavior.
This holds for all C++ versions since C++11 (before that the program would be obviously ill-formed).
If your compiler is printing garbage for a (after removing b from the program so that it doesn't have undefined behavior anymore), then this is a bug in the compiler.
Regarding edit of question:
There is no way to extend the lifetime of a temporary through binding to a reference member of a non-aggregate class.
Relying on this lifetime extension at all is very dangerous, since you would likely not get any error or warning if you happen to make the class non-aggregate in the future.
If you want the class to always retain the object until its lifetime ends, then just store it by-value instead of by-reference:
class B
{
std::string str;
public:
B(std::string str) : str(std::move(str)) {}
void print() { std::cout << str << '\n'; }
};

How does constant reference makes a difference?

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.

Strange behaviour with operator[]

Having spent the last couple of days experimenting with C++ operator[] methods, I've come across an anomaly that I can't easily explain. The code below implements a simple test string class, which allows the user to access single characters via its subscript "operator" method. As I would like to differentiate between lvalue and rvalue subscript contexts, the "operator" method returns an instance of the custom "CReference" class as opposed to a standard C++ character reference. Whilst the "CReference" "operator char()" and "operator=(char)" methods appear to handle each rvalue and lvalue context respectively, g++ refuses to compile without the presence of an additional "operator=(CReference&)" method as documented below. Whilst the addition of this method appears to placate some kind of compile-time dependency, it is never actually invoked at run-time during the execution of the program.
As someone who thought they had acquired a fundamental understanding of C++ intricacies, this project has certainly proved to be a humbling experience. If anyone could see their way to enlightening me as to what's going on here, I would be eternally grateful. In the meantime, I'm going to have to pull out the C++ books in order to reconcile the void** between what I know and what I think know.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// Class for holding a reference to type char, retuned by the "operator[](int)"
// method of class "TestString". Independent methods permit differentiation
// between lvalue and rvalue contexts.
class CReference
{
private:
char& m_characterReference;
public:
// Construct CReference from char&
CReference(char& m_initialiser)
: m_characterReference(m_initialiser) {}
// Invoked when object is referenced in a rvalue char context.
operator char()
{
return m_characterReference;
}
// Invoked when object is referenced in a lvalue char= context.
char operator=(char c)
{
m_characterReference = c;
return c;
}
// NEVER INVOKED, but will not compile without! WHY???
void operator=(CReference &p_assignator){}
};
// Simple string class which permits the manipulation of single characters
// via its "operator[](int)" method.
class TestString
{
private:
char m_content[23];
public:
// Construct string with test content.
TestString()
{
strcpy(m_content, "This is a test object.");
}
// Return pointer to content.
operator const char*()
{
m_content[22] = 0;
return m_content;
}
// Return reference to indexed character.
CReference operator[](int index)
{
return m_content[index];
}
};
int main(int argc, char *argv[])
{
TestString s1;
// Test both lvalue and rvalue subscript access.
s1[0] = s1[1];
// Print test string.
printf("%s\n", (const char*)s1);
return 0;
}
The line s1[0] = s1[1]; causes the compiler to generate an implicit copy assignment operator for CReference if you didn't declare one yourself. This causes an error because your class has a reference member, which can't be copied.
If you added an assignment operator that takes a parameter of type const CReference&, it would get called by the assignment.
In your code, you declared a copy assignment operator of type void operator=(CReference &p_assignator). This can't be called because the righthand side of the assignment is a temporary object, which can't be bound to a non-const reference. However, the act of declaring this operator causes the compiler not to try to define an implicit copy assignment operator, and therefore avoids the previous compilation error. Since this operator can't be called, the compiler goes for the other assignment operator that takes a parameter of type char.
What's happening is that without the definition of operator=(CReference&),there are two possible overloads of operator=: the implicitly-defined operator=(const CReference&) and your operator=(char). When it tries to compiled s1[0] = s1[1], it tries to find the best match for operator=, which is the implicitly-defined operator=(const CReference&). But, that isn't allowed to be implicitly-defined, because CReference contains a reference member, so you get an error.
Conversely, when you do define operator=(CReference&), there's no longer an implicitly-defined assignment operator. Sections 12.8 clauses 9-10 of the C++03 standard state:
9) A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&.109)
10) If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly.
The implicitly-declared copy assignment operator for a class X will have the form
X& X::operator=(const X&)
if
— each direct base class B of X has a copy assignment operator whose parameter is of type const B&, const volatile B& or B, and
— for all the nonstatic data members of X that are of a class type M (or array thereof), each such class type
has a copy assignment operator whose parameter is of type const M&, const volatile M& or
M.110)
Otherwise, the implicitly declared copy assignment operator will have the form
X& X::operator=(X&)
So, when it tries to compile s1[0] = s1[1], it has two overload choices: operator=(CReference&) and operator=(char). It choose operator=(char) as the better overload because it can't convert a temporary object (the result of s1[1]) into a non-const reference.
I compiled your program on Ideone by removing the overloaded = and it gives the following error:
prog.cpp: In member function ‘CReference& CReference::operator=(const CReference&)’:
prog.cpp:9: error: non-static reference member ‘char& CReference::m_characterReference’, can't use default assignment operator
prog.cpp: In function ‘int main(int, char**)’:
prog.cpp:70: note: synthesized method ‘CReference& CReference::operator=(const CReference&)’ first required here
As the error points out,
s1[0] = s1[1];
needs the copy assignment operator for CReference class.
You have a reference member variable in your CReference class
char& m_characterReference;
For classes with a reference member, you need to provide your own implementation of = operator as a default = cannot infer what to do with the reference member. So you need to provide your own version of the = operator.
So a little bit of speculation here:
With,
s1[0]=s1[1]
Ideally, for CReference& CReference::operator=(const CReference&) to be invoked the argument should be a const CReference& but the argument s1[1] returns a non constant CReference&, so:
why does the compiler asks for CReference& CReference::operator=(const CReference&) in the code sample link I posted &
Why does the error go away if we provide a = operator with a non constant argument, like CReference& CReference::operator=(CReference&)
As it seems in the absence of an explicit non constant argument copy constructor the compiler does some kind of a optimization and treats the argument of s1[1] as an constant thus asking for the constant argument copy assignment operator.
If a non constant argument copy assignment operator is explicitly provided the compiler doesn't perform its optimization but just uses the one that is already provided.
Instead of returning a CReference, you can return a char&. That would remove the need for the CReference class, which is dangerous for the reasons that I included in my comment above. You can have two forms of the operator[], one returning char and one returning char&.
OK, let's see:
s1[0] = s1[1];
s1[0] returns CReference
s1[1] returns CReference
Of course you need an assignment operator that accepts CReference.
Hence your statement in bold letters:
// NEVER INVOKED, but will not compile without! WHY???
void operator=(CReference &p_assignator){}
is incorrect.
I think your
void operator=(CReference &p_assignator){}
is invoked since
// Test both lvalue and rvalue subscript access.
s1[0] = s1[1];
use the function, as both s1[0] and s1[1] return CReference. and not char.
Also you would like to do something like this
CReference & operator=(CReference &p_assignator){}
to handle
s1[0] = s1[1] = s1[2]; //etc...
UPDATED: a thought
Why we need to provide user-defined copy assignment operator?
Answer: I think the default behavior of the implicit assignment operator is to re-assign the referenced member variable to refer to another location instead of the semantic in m_characterReference = p_assignator.m_characterReference;. Reference variable for us is a syntactic sugar but for the compiler it is a constant pointer. Thus, the default assignment tries to re-assign this constant pointer.
The two functions CReference::operator char() and CReference::operator=(char) do not represent lvalue and rvalue.
As previously mentioned, your main mistake is that you declared the copy assignment operator to return void. Thus, it won't be invoked.
For the compiler to execute "s[0] = s[1];" and based on your code, first, s[x] will be converted to CRefernce anonymous objects. Then the compiler search for operator=(). Since you provide operator=(char), the compiler is "smart" and try to fulfill this assignment. So, it will invoke operator char() first to convert right-hand side (which CReference(s[1]) to char, then invoke operator=(char) function. The expression (s[0] = s[1]) becomes of type char.
You can avoid this conversion by modifying your code as
class CReference
{
private:
char& m_characterReference;
public:
// Construct CReference from char&
CReference(char &m_initialiser)
: m_characterReference(m_initialiser) {}
char& operator = (const CReference &r) {
m_characterReference = r.m_characterReference;
return m_characterReference;
}
};
The assignment operator returns char if you would like to keep the type of "s1[0] = s1[1]" as character. You should not worry about conversion from char to CReference (in case of s1[0] = s1[1] = s1[2]) since 1-arg constructor will handle this conversion.
A simple and clear example that represents lvalue and rvalue is
class Point {
public:
int& x() {return x_value; }
int& y() {return y_value; }
private:
int x_value;
int y_value;
};
int main()
{
Point p1;
p1.x() = 2;
p1.y() = 4;
cout << p1.x() << endl;
cout << p1.y() << endl;
}

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.