C++ const lost on reference [duplicate] - c++

This question already has answers here:
Pointers and References as member variables of const objects
(2 answers)
Closed 5 years ago.
Why does this compile?
struct A {};
struct B {
B(A& _a) : a(_a) {}
A &a;
};
void f1(A&) {}
void f2(const B &b) { f1(b.a); }
int main() {
A a;
B b{a};
f2(b);
return 0;
}
Inside f2() b is const, so my understanding was that b.a should also be const. But it does compile and the compiler allows calling f1().
Replace 'A& a;' in struct B with 'A a;' and it no longer works.
Now in f1() b.a indeed is const:
invalid initialization of reference of type 'A&' from expression of type 'const A'
Please help me understand this... Thanks.

When an object is const, it doesn't cause the reference members to also become const since the referent is not a part of the object itself. The reference member is just a piece of information representing the address of some other object. Whether or not the B object itself is immutable shouldn't affect whether it should be possible to mutate the objects it references.
If you make the B::a member a non-reference, as in A a;, then the B object will actually contain within itself an A object, so when the former is const, the latter will be too.

Inside f2() b is const, so my understanding was that b.a should also be const.
It is. If the instance is const then it's members will be, too. But look at the type of the member:
A & a;
That's a reference to A. Making that const yields a constant reference to A:
A & const a;
Not a reference to a constant A.

Strictly speaking there are no constant references. There are references to constant objects.
In the class B the data member a is declared like a reference to non-constant object of the type A.
A &a;
and this reference is passed as an argument to a function that accepts a reference to a non-constant object
void f1(A&) {}
void f2(const B &b) { f1(b.a); }
Thus the code compiles successfully.

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.

How to handle non-const lvalue reference to type 'A' cannot bind to a value of unrelated type 'B' for this pointer?

Currently I am running into catch 22 situation. I have the following code.
#include <iostream>
class B;
class A{
public:
A(B& _b):b(_b){}
private:
B& b;
};
class B{
public:
B(int _x):x(_x),a(*this){}
private:
A& a;
int x;
};
int main(){
B b(1);
}
I use forward declaration here to pass object of class B as parameter in class A. Then I use rvalue reference of class B's this pointer to pass it to A's constructor. So I know why the compiler is complaining (because of trying to bind rvalue to lvalue reference -- at least this is what I think is happening -- please correct me if I am wrong).
To solve this I try to do
#include <iostream>
class B;
class A{
public:
A(B _b):b(_b){}
private:
B b;
};
...
This again gives error saying field has incomplete type 'B' (I used to see this error only when "xxx.h" was not included since compiler did not know the class size) but they are in same Translation unit. Why still is the compiler complaining? Is there any solution to this problem of passing rvalue this object or am I falling into bad design?
For future read : In addition to peppe's answer this link also has good description
You're not building any A object. You're trying to set a (a lvalue reference) to the temporary A object built using the A(B &b) constructor.
In the a(*this) expression in the B(int) constructor's initialization list, *this yields a B&, which as-is cannot bind to a A& as the two classes are not related.
So, implicit conversions are tried. There's a way to get an A out of a B&: apply the A(B &) constructor to get a temporary A object. However, now you've got a temporary A, and that cannot bind to a, which is a non-const lvalue reference. A temporary can only bind to const lvalue references, or rvalue references.
And this is precisely what the compiler is telling you:
<source>: In constructor 'B::B(int)':
<source>:16:23: error: invalid initialization of non-const reference of type 'A&' from an rvalue of type 'A'
B(int _x):x(_x),a(*this){}
^~~~~
<source>:6:5: note: after user-defined conversion: A::A(B&)
A(B& _b):b(_b){}

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++ Templates Question

Can someone explain the output of the following code?
#include <iostream>
template <class T>
void assign(T& t1, T& t2){
std::cout << "First method"<< std::endl;
t1 = t2;
}
template <class T>
void assign(T& t1, const T& t2) {
std::cout << "Second method"<< std::endl;
t1 = t2;
}
class A
{
public:
A(int a) : _a(a) {};
private:
friend A operator+(const A& l, const A& r);
int _a;
};
A operator+(const A& l, const A& r)
{
return A(l._a + r._a);
}
int main ()
{
A a = 1;
const A b = 2;
assign(a, a);
assign(a, b);
assign(a, a + b);
}
The output is
First method
Second method
Second method
I don't see why. Shouldn't the last call to assign activate the first version, since (a+b) doesn't return a const A object?
An expression doesn't only have a value and a type, but it also has a value category. This category can be
An lvalue: These expressions generally refer to declared objects, references, functions or dereference results of pointers.
An xvalue: These are the result of generating an unnamed rvalue reference. Rvalue references are created by T&& instead of T&. They are a C++11 concept, and you can ignore them here. Mentioned only for sake of completeness.
An prvalue: These are the results of casts to non-reference types (like A(10)) or computing/specifying a value, like 42 or 2 + 3.
An lvalue reference requires an lvalue expression for initialization. That is, the following is invalid:
A &x = A(10);
The reason behind this is that only lvalue expressions refer to things that are suitable and intended for staying alive a longer time than only for the duration of the initialization. Like, a declared object is alive until exiting its block (if it was a local non-static variable) or until the end of the program (if it was declared outside functions and classes). The rvalue expression A(10) refers to an object that dies already when the initialization is finished. And if you said the following, it would not make any sense of all, because pure values like 10 don't have an address at all, but references require some sort of identity to which they bind, which in practice is implemented by taking the address of their target internally in compilers
int &x = 10; // makes totally no sense
But for const references, C++ has a backdoor. When initialized with a prvalue, a const lvalue reference will automatically lengthen the lifetime of the object, if the expression refers to an object. If the expression has a non-object value, C++ creates a temporary object with a value of that expression, and lengthens the lifetime of that temporary, binding the reference to that temporary:
// lifetime of the temporary object is lengthened
A const& x = A(10);
// lifetime of the automatically created temporary object is lengthened
int const& x = 10;
What happens in your case?
Now the compiler in your case, because you supply a temporary object, will choose the version that has a A const& parameter type rather than a A& parameter type.
(a + b) returns a temporary object, though, and can therefore only be bound to a constant reference.
a+b returns a temporary, if you were allowed to catch a non-const reference to it you would be able to change it and then what? The temporary goes out of scope, and the changes done to it can never be captured by the application. In C++03 temporaries will be bound to const references types.
By the way, this has nothing to do with templates. Rewrite your example to use straight 'A's and you will observe the same behavior.

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.