Search code examples
c++templatesconst-correctness

Const-correctness with proxy object


I am confused with the following code, where the class Baz gives access to its internal data through the proxy class Bar:

struct Foo{};

template<class T>
struct Bar 
{
    Bar(T &val_): val(val_) {}

    T &val;
};

struct Baz 
{
    Bar<const Foo> get() const {return Bar<const Foo>(foo);}
    Bar<Foo>       get()       {return Bar<Foo>      (foo);}
    Foo foo;
};

struct FooBaz
{
    FooBaz(Baz &baz_): baz(baz_) {}
    // Bar<const Foo> get() const {return baz.get();} // this do not compile
    Bar<const Foo> get() const {return const_cast<const Baz &>(baz).get();} // the const_cast seems to be required
    Baz &baz;
};

It seems that I must do a const_cast to dispatch to the right Baz::get() function.

The first thing that I don't understand is that it works if the FooBaz::baz is a plain Baz and not a reference:

struct Foo{};

template<class T>
struct Bar 
{
    Bar(T &val_): val(val_) {}

    T &val;
};

struct Baz 
{
    Bar<const Foo> get() const {return Bar<const Foo>(foo);}
    Bar<Foo>       get()       {return Bar<Foo>      (foo);}
    Foo foo;
};

struct FooBaz
{
    FooBaz(Baz &baz_): baz(baz_) {}
    Bar<const Foo> get() const {return baz.get();} // Ok
    Baz baz;
};

What I understand about a const function member is that it makes the this pointer-to-const, but in fact it is not totally clear to me what it means... Do all the data member be 'as if they are const'? Is there a clear reference to that?

Another interesting thing is that if I use a std::reference_wrapper as proxy class, then it works without the const_cast:

#include <functional>

struct Foo {};

struct Baz 
{
    std::reference_wrapper<const Foo> get() const {return std::cref(foo);}
    std::reference_wrapper<Foo>       get()       {return std::ref (foo);}
    Foo foo;
};

struct FooBaz
{
    FooBaz(Baz &baz_): baz(baz_) {}
    std::reference_wrapper<const Foo> get() const {return baz.get();} // Ok
    Baz &baz;
};

What is the magic with the std::reference_wrapper?

Thanks!


Solution

  • I must do a const_cast to dispatch to the right Baz::get() function.

    Yes. Or std::as_const, or make Bar<T> convertible to Bar<const T>.

    What I understand about a const function member is that it makes the this pointer-to-const, but in fact it is not totally clear to me what it means... Do all the data member be 'as if they are const'?

    Yes, the members behave as if they were const.

    The expression E1->E2 is exactly equivalent to (*E1).E2 for built-in types; that is why the following rules address only E1.E2.

    In the expression E1.E2:

    1. if E2 is a non-static data member:

      if E2 is of reference type T& or T&&, the result is an lvalue of type T designating the object or function to which E2 refers, otherwise, if E1 is an lvalue, the result is an lvalue designating that non-static data member of E1, otherwise (if E1 is an xvalue (which may be materialized from prvalue)), the result is an xvalue designating that non-static data member of E1.

      If E2 is not a mutable member, the cv-qualification of the result is the union of the cv-qualifications of E1 and E2, otherwise (if E2 is a mutable member), it is the union of the volatile-qualifications of E1 and E2;

    cppreference

    When accessed through a pointer to const class, its members become const.

    But the constness is only added at the top level. If you access int *member; through a pointer to const, it becomes int *const member;, not const int *const member;.

    References work in a similar way, but since references themselves can't be const1, their type is not changed.

    if I use a std::reference_wrapper as proxy class, then it works without the const_cast

    That's because it a has a constructor that allows std::reference_wrapper<const T> to be constructed from std::reference_wrapper<T>.


    1 Yes, a reference can't changed to point to a different object after it's created, but formally it's not const. std::is_const returns false for references.