Most explanations of const are based on simple types, but what happens with objects containing references to other objects? Is const-ness shallow or infinitely deep/recursive? (Assuming the keyword mutable is never used).
In other words, if an object is passed as const & and contains references/pointers to other objects and so forth, can I rely on the facts that no object in the chain will ever be modified in any way?
Does it also mean that no reference can ever be returned to the main code that would allow modifying an underlying object or one of its children?
Finally, does it mean that the signature of every function/method that might receive this object or one of its children needs to be such that it also guarantees the const-ness of every child of the object and this recursively?
> if an object is passed as const & and contains references/pointers to other objects and so forth,
> can I rely on the facts that no object in the chain will ever be modified in any way?
No, It is the programmer's responsibility to propagate the constness to pointed-to/referred objects.
#include <iostream>
#include <experimental/propagate_const>
struct A
{
void foo() { std::cout << "A::foo()\n" ; }
void foo() const { std::cout << "A::foo() const\n" ; }
};
struct B
{
explicit B( A& a ) : pa( std::addressof(a) ) {}
// note that in this function, the pointer pa is deemed to be const
// this means that the function will not be able to modify the value of pa
// however, the object pointed to by pa is not const qualified
void bar() const { std::cout << "B::bar() const calls " ; pa->foo() ; }
private: A* pa ;
};
struct C
{
explicit C( A& a ) : pa( std::addressof(a) ) {}
// here, the const-ness is propagated to the object pointed to (by the wrapper)
void bar() const { std::cout << "C::bar() const calls " ; pa->foo() ; }
private: std::experimental::propagate_const<A*> pa ;
};
int main()
{
A a ;
const B b(a) ;
b.bar() ;
const C c(a) ;
c.bar() ;
}
Thanks a lot for your thoughtful response and for pointing at the example.
In the open-std.org example, the non-const smart pointer is “internal” to the main object, i.e. it is created within the class constructor and doesn’t reference any object passed to the constructor. The example is somewhat surprising but I am not too shocked that the language would allow the programmer such latitude in designing the guts of a class. There was already no promise that a const object could not be changed from outside, for example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include <iostream>
#include <vector>
class box{
public:
const std::vector<int> & v;
box(const std::vector<int> & v ) :v(v){}
};
int main()
{
std::vector<int> v = {1,2,3,4,5,6};
constauto c = box(v);
v[3] = 99;
std::cout << c.v[3] << std::endl;
}
But what about the no-side-effect promise that if I pass an object as const reference to a class constructor, no class method can ever be used to alter the object or any of its children? For example, in the example above, can the box object ever be used in some way to modify the underlying vector<int>?
Thanks for the follow up (sorry for posting, deleting and reposting a slightly different response). I do see that side effects are possible because there is alwys the ugly std::addressof() that can cast anything into anything and thus break any type logic. Is there any valid reason to ever use std::addressof() unless you working on low level hardward code?
Note that removing a const qualifier through casting and then writing to the now-non-const pointer has may have undefined behavior. So while you can in principle write to const references by casting, it doesn't really matter because a program that does such a thing has a bug anyway.