Is const-ness deep/recursive ?

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?



Last edited on
> 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.

Library Fundamentals v2 (TS) has the wrapper std::experimental::propagate_const
https://en.cppreference.com/w/cpp/experimental/propagate_const

Proposal: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4388.html
Last edited on
Here is a trivial example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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() ;
}

http://coliru.stacked-crooked.com/a/208724fdb260b12d
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};
    const auto 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>?
Last edited on
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?
Last edited on
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.
Last edited on
> For example, in the example above, can the box object ever be used in some way
> to modify the underlying vector<int>?

No. Not without explicitly casting away the const.


> because there is alwys the ugly std::addressof() that can cast anything into anything

std::addressof() won't cast away constness or volatility.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <memory>
#include <type_traits>

int main()
{
    const volatile int i = 23 ;

    auto ptr = std::addressof(i) ;
    static_assert( std::is_same< decltype(ptr), const volatile int* >::value ) ;

    std::cout << "ok\n" ;
}

http://coliru.stacked-crooked.com/a/eb6da231cf51e4da


> Note that removing a const qualifier through casting
> and then writing to the now-non-const pointer has undefined behavior.

Only if the object being modified is a const object (it is of a const-qualified type).
Fair enough. It's pretty silly to do this, though:
1
2
3
void foo(const int &i){
    const_cast<int &>(i) = 42;
}
Topic archived. No new replies allowed.