views:

642

answers:

7

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.

+7  A: 

Your example doesn't fail, k is passed by value. The member i is 'implicitly constant' as direct members of C can't be changed when the instance is constant.
Constness says that you can't change members after initialization, but initializing them with values in the initialization list is of course allowed - how else would you give them a value?

What doesn't work is invoking the constructor without making it public though ;)

update addressing updated question:

Yes, C++ forces you into some verboseness sometimes, but const correctness is a common standard behaviour that you can't just redefine without breaking expectations. Pavels answer already explains one common idiom, which is used in proven libraries like the STL, for working around this situation.

Sometimes you have to just accept that languages have limitations and still deal with the expectations of the users of the interface, even if that means applying an apparently sub-optimal solution.

Georg Fritzsche
+1 for the sharp eyes :)
Anders K.
Of course, my fault. I modified an example to use pointers
doc
+1. Simple and informative. Just don't know what to add.
Dmitriy Matveev
@doc: Then you could do `const_cast<int*>(p)` - but you should think twice about what you're doing before promoting const pointers to non-const.
Georg Fritzsche
OK thanks for the fast reply
doc
I really apologize for my absend-mindednes. Because what said Pavel Minaev I must unaccept your answer. After all I think it's not a bad question and I didn't find satisfactory explanation on the net.
doc
Sure, accept what fits best for you.
Georg Fritzsche
Great answer :-).
Mads Elvheim
-1 This doesn't really address the meat of the issue.
Mordachai
You know, there was some editing in the question since i answered, you could check for yourself first.
Georg Fritzsche
A: 

Your question does not make sense. Where did you get all these "this will fail" predictions? None of them are even remotely true.

Firstly, it is completely irrelevant whether the constructor's parameter is declared const or not. When you are passing by value (as in your case) you can pass a const object as an argument in any case, regardless of whether the parameter is declared as const or not.

Secondly, from the constructor's point of view, the object is NOT constant. Regardless of what kind of object you are constructing (constant or not), from within the constructor the object is never constant. So there's no need for mutable or anything.

Why don't you just try compiling your code (to see that nothing will fail), instead of making strange ungrounded predictions that something "will fail"?

AndreyT
sorry it was a mistake. Class is using pointers so it isn't pass by value. I have real class of this kind, I just wanted to show it clean and oversimplified an example.
doc
A: 

Hi,

I didnt understand your solution. I am very much new to stackoverflow. I dont know where to post the replies. so i am posting it in the Answers section. sorry for that.

Although the constructor is made public, VS2008 still gives compiler error for the above program. Can you explain me in detail, if you dont mind?

AH
just remove both method(). They are only to show that int * i can not be made const.
doc
+9  A: 

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.

Pavel Minaev
Yes you are right. So a separate class would be the best solution. Is it advisable to inherit everything after C or you have to rewrite all the code? Consider C has dozens of int * pointers - then inherited version const_C would waste space allocated by C's non-const members. On the other hand repeating all the code is a cumbersome. Any sugestions?
doc
The real problem with inheritance isn't duplication of fields - you can refactor those into a struct, and then apply `const` to that wholesale in your `const_C` version. The problem is code reuse. First of all, inheriting `const_C` from `C` doesn't make sense - you want `C` to be implicitly convertible to `const_C`, not the other way around, so you'd have to derive `C` from `const_C` if you want to use inheritance for that. But then the problem is that all fields you inherit from `const_C` will also be `const`, and mutating `C` methods will have nothing to work with...
Pavel Minaev
For the nth time I apologize for my terrible expression. Of course you inherit after const_C, then consider that const_C has (maybe not dozens but a few) const ints or any non-pointer const data which should be allocated on stack. In such case this is also a problem for extended class if it has to redefine all of them to non-const. And code reuse is of course the second problem.
doc
In case of fields one may use mutable. But many docs say that mutable should be used if field is logicaly const but for some reason needs update. And here it would be mutable by design.
doc
`mutable` is used when _object_ is logically const, but some of its fields aren't (e.g. if said fields serve as a cache of sorts).
Pavel Minaev
Ok I can now focus on this topic"then const applied to it should only mean that it cannot be changed itself, and should not imply anything regarding the value pointed to"I disagree. It would be the case if const would be applicable to fields only and not values they are pointing to. But it isn't! And this brings some inconsistency to const-correctness. If the class is obligged not to modify itself, it seems logical to me that this semantics should affect recursively all fields. Only thanks to side effect of pass-by-value it isn't necessary to write const version of class very often.
doc
Side effect - I mean, const argument's value may be copied to non-const class field.
doc
In C++, `const` is shallow _by design_, and does not propagate through pointers and references. For example, a `const auto_ptr<T>` lets you modify the object it points to. If you want to reference a const object, you specify it separately, as in `auto_ptr<const T>`. Note also that `const` _does_ apply recursively to all fields of a `const` class/struct (unless they're declared `mutable`) - but when such fields are pointers, only the pointers themselves are `const`, and not what they point to. If you want a propagating `const` pointer, it's trivial to write it yourself.
Pavel Minaev
> "but when such fields are pointers,> only the pointers themselves are> const, and not what they point to"And this is what I called inconsistency. Because same construct one time behaves in very same way and another time different. In fact it *does not* apply recursively to *all* fields since pointer may be a field like any other. To be precise `const` is deep but with recursion level set to 1. Otherwise it won't be able to make value pointer is pointing to const.
doc
I think there is some confusion here - `const` does apply recursively to arbitrary depth, but only to _fields_ (and base class subobjects), not to pointer dereference - since an object referenced by a pointer is not logically the part of the object which contains said pointer. So if you have structs `A`, `B` and `C`, where a field in `A` is of type `B`, and field in `B` is of type `C`, applying `const` to `A` will propagate all the way to fields of `C`, including pointers - so `int x` will become `const int x`, and `char* p` will become `char* const p`.
Pavel Minaev
Exactly. And the problem is that const methods handle constness of non-pointed fields (including pointers) but not const pointer targets like `const char * p`. This is because for pointers you have two various worlds on which const can be applied. You can say that you have: const 1 and const 2. But class grammar can handle easily only const 1. Consider what would happen without const methods. If C++ provides machanisms for marking pointer target const it should provide grammar to handle this all the way.
doc
And as I said before, only thanks to copy-by-value nature of non-pointer fields you don't need to create two versions of class: const and non-const very often. Const value passed to the constructor may be copied to non-const field, then instance even may be marked as const... But this is somewhat slippery.
doc
Pointers are not fundamentally different from any type that is "produced" from some other type. A pointer is `T*`, and a const pointer is `T* const`. The fact that `T` itself can be `const U` (and that `U` can in turn be a pointer type `V*`, and so on ad infinitum) is irrelevant to the pointer itself. Similarly, you have `auto_ptr<T>`, and `T` can itself be `const U`, and then `U` can itself be `auto_ptr<V>`, and so on. Const-propagation doesn't make sense in all use cases, however, not even if we only look at pointers. But you can still write your own pointer wrapper class that does it.
Pavel Minaev
Yeah I know that pointer can be T * const and how things are. What I am trying to say is that implementation of this is somewhat ugly. When solution to my question, which is rather basic problem, requires template meta programming then I used to think that something is wrong in the language construct; when ppl need `mutable` keyword, `const_casts` and so on. In my opinion it would be much better if const would propagate on *all* possible targets. Tell me why assumption that const should affect const instance only in the way `T * const` and not `const T * const` is better.
doc
And you are wrong that pointers are not different than any other types. In terms of const, they are different, I think they are one and only construct for which const can be applied in two places - for pointer itself and value it is pointing to.
doc
All other structures are affected by const once. Have you got for example methods like `m() const const`? Which might mean that method does not change object's state and for example in addition it does not allocate memory. This is how pointers are.
doc
Pavel Minaev
Similarly, by your logic, you could say that for `auto_ptr<T>`, there are "two places" for const - `const auto_ptr<T>`, and `auto_ptr<const T>`. It is _exactly_ the same as with a raw pointer. Of course, only in the first case, `const` appies to `auto_ptr` - in the second case, `const` is just a part of a type-specifier `T`.
Pavel Minaev
As to why it's better - because your case isn't the only one? For example, imagine that you've got two objects, and the second object is always bound to the first (gets a pointer to it in its constructor, and will never be rebound to a different object during its lifetime). This is best represented by a const pointer (since we will never change its value) to a non-const object (since we will change its value). In more general terms, non-const-propagating pointers are more "fundamental", since you can write a const-propagating pointer in terms of non-const-propagating one, but not vice versa.
Pavel Minaev
And to clarify my previous comments. The trouble is that const methods, together with copy nature of arguments passed by value fit into fields like `const T` or `T * const`. None of this work with `const T * const` pointers. That's why you have to write separate classes for such cases. My idea to workaround this problem would be for example to provide const constructors which could promote fields to be const (note that const constructor would be called only for const instance creation).
doc
doc
Re: "Similarly, by your logic, you could say that for auto_ptr<T>" > In first case it belongs to auto_ptr instance (or whatever) in second case const affects template argument. If it would be for example `const const auto_ptr<T>` and that would make T const, then this might be my logic. But here it is obvious for anyone that `const const auto_ptr<const T>` would be a kind of nonsense. In case of `const T * const` pointers it is not so easily visible, but yes this is the same kind of nonsense.
doc
Your point of view is of course not bad. Just get out the box and realize that one time C++ assumes that `const int` is a type, and the other time it looks at it as a "promise". Ambiguous semantics in the core of the language.
doc
@doc, sorry, but you're just wrong regarding the pointer syntax. Please read the ISO C++ specification. In `const int* ptr`, `const` belongs to the pointee, not the pointer; you write `int* const ptr` to apply it to the pointer. The latter syntax is not unique to the pointer - you can also write `int const x` to declare `x` as const. For `int`, `int const` and `const int` is the same. However, in general, `const` binds "tighter" than `*` - so in `const int* p`, `const` binds to `int`, and then `*` binds to the result.
Pavel Minaev
I also agree that C++ declaration syntax is generally horrible, but, to be honest, binding of `const` is not the only thing that is messed up - consider the difference between e.g. `int* x[]` and `int (*x)[]` for another problem of the same kind. It would be much simpler if types always read left to right, and syntax was consistent for all type constructors, so we could write something like `var x : const<ptr<int>>`, or `var x: ptr<const<int>>`, and it would be immediately clear which is which. Nonetheless, messy as the current syntax is, its semantics is the same.
Pavel Minaev
As a side note, `const` behavior can be observed via `typedefs`. For example, if you `typedef int* ptr_to_int`, and then write `const ptr_to_int p`, type of variable `p` will be `int* const`, and not `const int*`.
Pavel Minaev
Getting back to the actual question - in truth the real problem here isn't non-propagation of const through pointers - it could be trivially solved by a wrapper template class that performs it. The real problem, as you've noted, is that there's no way to "overload" a constructor on constness of the constructed instance, and have all fields in a "const constructor" be appropriately const-qualified. It is a separate notion, and I agree that it would be quite useful (as your example demonstrates).
Pavel Minaev
They have two sub-senses of what "const pointee" means. One time it is very stict type, another time a "promise" that pointer will not change target's value. If it would be a strict type, then a lot of problems would gone because everyone would see obvious thigs that should be done. I have a lot of troubles with my code because of that pointers :( If you are interested I am wrapping math class, which operates on pointer passed to the constructor. Sometimes I need to pass `const real *` other times `real *`. I need two classes for this, but they are part of larger math structure...
doc
...I am operating on indefinite number of dimmensions and all this is just "a bit sophisticated"
doc
You might want to look at a related discussion: http://stackoverflow.com/questions/1822429/a-recurring-const-connundrum
Mordachai
Would you like to take a look at my "MutatedPtr" response and what you think about it?
doc
A: 

A const int* is not the same as a int* const. When your class is const, you have the latter (constant pointer to mutable integer). What you're passing is the former (mutable pointer to constant integer). The two are not interchangeable, for obvious reasons.

DrPizza
A: 

When you instantiate

const C c1(...)

Because c1 is const, its member i turns in to:

int* const i;

As someone else mentioned, this is called implicit const.

Now, later in your example, you attempt to pass a const int*. So your constructor is basically doing this:

const int* whatever = ...;
int* const i = whatever; // error

The reason you get an error is because you can't cast const to non-const. The 'whatever' pointer is not allowed to change the thing it points to (the int part is const). The 'i' pointer is allowed to change what it points to, but cannot itself be changed (the pointer part is const).

You also mention wanting your class to model a pointer. The STL does this with iterators. The model some implementations use is having a class called 'const_iterator' which hides the real pointer and only supplies const methods to access the pointed-to data. Then there's also an 'iterator' class which inherits from 'const_iterator', adding non-const overloads. This works nicely - it's a custom class which allows the same constness as pointers, where the types mirror pointers like so:

  • iterator -> T*
  • const iterator -> T* const
  • const_iterator -> const T*
  • const const_iterator -> const T* const

Hopefully that makes sense :)

AshleysBrain
I have read gcc sources for iterator classes and I found that this classes are so simple that they are reproducing the code for each method. Not my case at all. Also note that iterator doesn't model a pointer, it models a bunch of pointers. In my case class always consists const pointer (that is `T * const`), thus I don't need four variations of const.
doc
A: 

OK here's what I have done so far. To allow inheritance after const version of class without const_casts or additional space overhead I created an union which basically looks like ths:

template <typename T>
union MutatedPtr
{
protected:
 const T * const_ptr;
 T * ptr;

public:
 /**
  * Conversion constructor.
  * @param ptr pointer.
  */
 MutatedPtr(const T * ptr): const_ptr(ptr) {};

 /**
  * Conversion to T *.
  */
 operator T *() {return ptr;}

 /**
  * Conversion to const T *.
  */
 operator const T *() const {return const_ptr;}
};

When MutatedPtr field is declared, it ends up so that in const methods const_ptr is returned, while non-const ones get plain ptr. It delegates method's const-ness to pointer target which makes sense in my case.

Any comments?

BTW you can of course do similar thing with non-pointer types or even methods, so it looks that introducing mutable keyword wasn't necessary(?)

doc
The problem with this is that it won't portably work if you access the same `MutatedPtr` first as const, and then as non-const (which is what happens when it's a member of a const object - it will be non-const in constructor, but const in other methods). The reason why this is a problem is that in C++, you can only read from the union member that was last written to - i.e. if ctor writes into `ptr`, then reading later from `const_ptr` invokes U.B.
Pavel Minaev
There is an exception to that if union has two (or more) structs. If those structs share a "common initial sequence" of members, then that common sequence can be accessed from either struct once one of them is assigned. To be "common" the sequence of fields must contain "layout-compatible" types in the same order. And any type `T` is layout-compatible with `const T`. So if you wrap those pointers in structs, then it should be fully conformant.
Pavel Minaev
The reason why this won't work in general (and can't replace `mutable` for all cases) is because you cannot have members of types with non-trivial constructors or destructors in unions (so e.g. no `std::string`).
Pavel Minaev
doc
I have also found that you can have types with non-trivial constructors in unions, you just have to provide custom constructor, destructor, assignment operator and copy constructor. Here's example from Open Standards C++, which contains std::string as member of union: `union U { int i; float f; std::string s; };`"Since std::string (21.3) declares non-trivial versions of all of the special member functions, U will have an implicitly deleted default constructor, copy constructor, copy assignment operator, and destructor. To use U, some or all of these member functions must be user-declared."
doc
The problem is of course memory layout. Silly compiler or a compiler in its early stage of development, may implement union over a plain struct, so that addresses won't overlap.
doc
In my case, the other way would be to initialize both: `const_ptr` and `ptr`. Since they are pointers I don't care where are they laying in the memory, I only care about their targets.
doc