views:

574

answers:

5

I have two getter members:

Node* prev() { return prev_; }
int value()  { return value_ }

Please note the lack of const identifiers (I forgot them, but now I want to know why this won't work). I am trying to get this to compile:

Node(Node const& other) : prev_(other.prev()), value_(other.value()) { }

The compiler rejects this. I thought that C++ allows non-const to const conversion in function parameters, such as:

{
   Foo(int bar);
}

Foo(const int bar)
{
   //lala
}

Why won't it let me do the same thing with a copy constructor? The const identifier means I promise not to change anything, so why would it matter if I get my value from a const or non-const source?

+15  A: 

You're not trying to do a non-const to const conversion. You're attempting to call two methods which are not const an a const reference (calls to prev and value). This type of operation is strictly forbidden by const semantics.

What you could do instead is use the fields prev_ and value_ directly. Because it's a member of the same type you can access privates which will be available on the const object.

JaredPar
+2  A: 

You're right - the const means you promise not to change anything. The compiler is making you keep your promise by not allowing you to call methods on that object which do not themselves promise not to change anything.

Tyler McHenry
So putting const after the parentheses tells my copy ctor that those CANNOT mutate other's data?
Hooked
it's not about the constructor, it's the accessors that aren't const, so can't be called on a const object.
Javier
So my compiler is just paranoid. Even thought my accessors don't mutate the data, the copy constructor doesn't know this?
Hooked
It's not paranoid, it's following the C++ standard. Methods are not assumed to be const unless you explicitly say they are by including the const keyword. For such a simple method, yes, the compiler could conceivably do the static analysis necessary to determine that you're not actually changing anything, but it's still not allowed in the C++ standard to call a non-const method on a const object.
Tyler McHenry
Not your compiler - the language itself. C++ does not require your compiler to guess that you forgot to declare the accessor const just because your current implementation (which could be in another compilation unit) doesn't mutate the data.
VoiceOfUnreason
So you know, the reason why this rule exists is that it may be (and often is the case) that the code for the copy constructor and the code for the accessor methods are in different compilation units. This means that when compiling the constructor, the compiler only sees the prototype for the accessor method, and does not see the code within it. Therefore the compiler has to be able to tell whether the call is going to be modifying or not based only on the method prototype; hence the need for a const qualifier on the method prototype.
Tyler McHenry
Also, the function might be deliberately non-const because although it doesn't *currently* mutate the object, it will do in future, or its part of an interface where other implementations might mutate. Then you wouldn't want the compiler to "helpfully" let you call it anyway, allowing you to write code which will break when the anticipated future change is made.
Steve Jessop
+3  A: 

I thought that C++ allows non-const to const conversion in function parameters, such as:

You are trying to do the exact opposite: Const to non-const. Calling a non-const member function, the compiler will bind the expression (which is a Node const to a Node& for binding the this pointer). It thus will drop const - not allowed because it would call a non-const function on a const object.

/* class Foo */ {
   Foo(int bar);
}

/* Foo:: */ Foo(const int bar)
{
   //lala
}

Well that code is a whole other thing. It declares a function (constructor) two times, with the only difference being that one time the parameter is const, and another time it isn't. The type of the function is the same both times, and so they don't conflict (the const parameter will only have an impact within the function body locally - it won't make any difference to the caller). In addition, this code doesn't contain any call (assuming that the first block is within some function).

If you wonder: If the above two versions are identical - why isn't the below? Then this is because the below contains one other level of indirection: Below, the reference itself (the top level) is not const (you can't put const on a reference itself directly), but the type referenced is const. At this time, const is not ignored in determining the type of the function.

/* class Foo */ {
   Foo(int &bar);
}

// different thing, won't work!
/* Foo:: */ Foo(const int &bar)
{
   //lala
}
Johannes Schaub - litb
+2  A: 

In the declaration of the constructor Node(Node const& other) the argument other is declared const. This means you can only call const methods on it.

You are calling other.prev() and other.value() from the constructor both of which are non-const methods, hence the compiler complains.

atomice
+2  A: 

As others have mentioned, you're calling non-const methods on a const object which the compiler will not allow.

A simple fix for this is to just mark your getter functions as const:

Node* prev() const { return prev_; }
int value()  const { return value_; }

since they don't modify the object; now they can be called with a const object. In fact, this should probably be done anyway so that users of the class can call those getters with const objects.

However, you have another potential problem in that if your copy ctor looks like:

Node(Node const& other) : prev_(other.prev()), value_(other.value()) { }

the copy and the original will both point to the same Node object in their prev_ member. Depending on the class semantics this might be OK, but it might be an ownership issue or other logical inconsistency.

Michael Burr
Good catch on the ownership issue.
David Thornley