Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with initialization of non-const reference member in const object?

Let's say you have a class

    class C 
    {
      int * i;

      public:

         C(int * v):i(v) {};

         void method() const;  //this method does not change i
         void method();        //this method changes i
    }

Now you may want to define const instance of this class

    const int * k = whatever;
    const C c1(k); //this will fail

but this will fail because of non-const int C's constructor C(int * v)

so you define a const int constructor

    C(const int * v):i(v) {}; //this will fail also

But this will fail also since C's member "int * i" is non-const.

What to do in such cases? Use mutable? Casting? Prepare const version of class?

edit: After discussion with Pavel (below) I investigated this problem a bit. To me what C++ does is not correct. Pointer target should be a strict type, that means that you could not for example do the following:

int i;
const int * ptr;
ptr = & i;

In this case language grammar treats const as a promise not to change pointer's target. In addition int * const ptr is a promise not to change pointer value itself. Thus you have two places where const can be applied. Then you may want your class to model a pointer (why not). And here things are falling into pieces. C++ grammar provides const methods which are able to promise not to change field's values itself but there is no grammar to point out that your method will not change targets of your in-class pointers.

A workaround is to define two classes const_C and C for example. It isn't a royal road however. With templates, their partial specializations it's hard not to stuck into a mess. Also all possible arguments variations like const const_C & arg, const C & arg, const_C & arg, C & arg don't look pretty. I really don't know what to do. Use separate classes or const_casts, each way seems to be wrong.

In both cases should I mark methods which don't modify pointer's target as const? Or just follow traditional path that const method doesn't change object's state itself (const method don't care about pointer target). Then in my case all methods would be const, because class is modelling a pointer thus pointer itself is T * const. But clearly some of them modify pointer's target and others do not.

like image 501
mip Avatar asked Nov 09 '09 05:11

mip


1 Answers

Sounds like you want an object that can wrap either int* (and then behave as non-const), or int const* (and then behave as const). You can't really do it properly with a single class.

In fact, the very notion that const applied to your class should change its semantics like that is wrong - if your class models a pointer or an iterator (if it wraps a pointer, it's likely to be the case), then const applied to it should only mean that it cannot be changed itself, and should not imply anything regarding the value pointed to. You should consider following what STL does for its containers - it's precisely why it has distinct iterator and const_iterator classes, with both being distinct, but the former being implicitly convertible to the latter. As well, in STL, const iterator isn't the same as const_iterator! So just do the same.

[EDIT] Here's a tricky way to maximally reuse code between C and const_C while ensuring const-correctness throughout, and not delving into U.B. (with const_cast):

template<class T, bool IsConst>
struct pointer_to_maybe_const;

template<class T>
struct pointer_to_maybe_const<T, true> { typedef const T* type; };

template<class T>
struct pointer_to_maybe_const<T, false> { typedef T* type; };

template<bool IsConst>
struct C_fields {
   typename pointer_to_maybe_const<int, IsConst>::type i;
   // repeat for all fields
};


template<class Derived>
class const_C_base {
public:
    int method() const { // non-mutating method example
        return *self().i;
    }
private:
    const Derived& self() const { return *static_cast<const Derived*>(this); }
};

template<class Derived>
class C_base : public const_C_base<Derived> {
public:
    int method() { // mutating method example
        return ++*self().i;
    }
private:
    Derived& self() { return *static_cast<Derived*>(this); }
};


class const_C : public const_C_base<const_C>, private C_fields<true> {
    friend class const_C_base<const_C>;
};

class C : public C_base<C>, private C_fields<false> {
    friend class C_base<C>;
};

If you actually have few fields, it may be easier to duplicate them in both classes rather than going for a struct. If there are many, but they are all of the same type, then it is simpler to pass that type as a type parameter directly, and not bother with const wrapper template.

like image 82
Pavel Minaev Avatar answered Sep 21 '22 23:09

Pavel Minaev