Reputation: 22365
I'm a student who still doesn't quite get const parameters. I understand why this won't work:
#include <stddef.h>
struct Node {
int value;
Node* next;
};
Node* cons(const int value, const Node * next) {
Node * tmp = new Node();
tmp->value = value;
tmp->next = next;
return tmp;
}
int main () {
const Node * k;
k = cons(1, NULL);
Node * p;
p = cons(2, k);
}
This is because I am "casting away" the constness of k by giving p its address.
What I intended by marking the parameter "const" was to say that my method won't change the node directly. Where as if I were to pass in (Node * next), I would feel like there is no guarantee that my method is not dereferencing that pointer and messing up the node. Maybe this is foolish, since there is then no guarantee that later on the const Node I passed a pointer to won't get changed via the new pointer to it. It just seems strange that: in this method cons, all I am doing is pointing a new pointer at 'next' - I never touched next, just pointed - and yet that is enough to break the constness.
Maybe I have just underestimated the strength of the "const" promise.
Thanks!
Upvotes: 2
Views: 226
Reputation: 20031
Maybe this is foolish, since there is then no guarantee that later on the const Node I passed a pointer to won't get changed via the new pointer to it. It just seems strange that: in this method cons, all I am doing is pointing a new pointer at 'next' - I never touched next, just pointed - and yet that is enough to break the constness.
It isn't simply that you pointed, it's that you pointed with a non-const
pointer. If you make Node::entry
a const Node*
, things should work.
Of course, that limits what you can do later when manipulating your list.
There are effectively two different forms of const
in C++: "honestly-truly const
" and "const
as far as you're concerned right now." Most of the time you'll deal with the second kind, like in the example. However, your program needs to work correctly with both forms of const
to be const
correct:
int main()
{
const Node n = { 5, NULL };
Node* p = cons(6, &n);
}
It's undefined behavior to cast away const
on n
(or on a pointer to n
), and cons
must respect that. I should also mention that I believe part of your assumption is that you recognize that your function could modify the const Node*
, but argue that since the troublesome assignment is the last line of the method the compiler should recognize that it doesn't actually do anything wrong. I'm not aware of any rule in C++ that states "it is illegal to do X, unless of course you do it as the last line of a function." The assignment is not allowed regardless of whether it comes first or last in the function.
You might be able to get the design you want and have const
correctness as well. For instance, you are allowed to overload cons
to take either a const Node*
or a Node*
and build up lists of const
or non-const
elements accordingly. If I knew more of what you want to do, I might have a better answer on how to get there.
Upvotes: 1
Reputation: 208323
Maybe I have just underestimated the strength of the "const" promise.
So what is the promise that you made? The function takes a copy of a pointer to a Node
and promises not to allow any modification of that Node
. The promise is not about the pointer, that would be Node * const
(and rather useless, since it is passed by value and the const
at that level is removed from the signature), but about the pointee, the Node
.
To provide a real life example, consider that a friend has offered to water your plants while you are on holidays, under the promise (usually implicit) that he will not steal anything from your house. Now you make a copy of the key and give it to your friend, who makes a copy of the key himself and gives it to a third party that never promised not to rob you. Do you consider that your friend broke his promise? How strong was his promise?
The code above has exactly the same pattern, cons
promises that you can give it a copy of a pointer to a Node
and that it won't allow it to be modified, but as soon as you enter the code, he copies that pointer to a different pointer that makes no promises whatsoever about the Node
, and then hands the copy away to whoever might be interested.
Upvotes: 3
Reputation: 476970
If T
is any type, then there are two related pointer types, T *
and const T *
.
You obtain pointers in nature by taking the "address-of" some object of type T
. The type of pointer depends on the constness of the object:
T x;
const T y;
T * p1 = &x; // OK, &x is T*
// T * p2 = &y; // Error!
const T * q1 = &y; // OK, &y is const T *
const T * q2 = &x; // also fine
So if we have a mutable object, a pointer to it is naturally a pointer-to-mutable. However, if we only have a constant object, then a pointer can only be a pointer-to-constant. Since we can always treat a mutable object as a constant one (just don't touch), a pointer-to-mutable can always be converted to a pointer-to-const, as in the case of q2
.
This is what's happening to your k
: It's a const T *
(with T = Node
), and we're assigning it the value of a T *
, which is fine, since we just ignore the fact that we could have mutated T
if we liked.
[In real life, we usually have constant references rather than flat-out constant objects, but you can treat those essentially as the same thing (since a reference is just an alias for an object and functionally identical to the object itself).]
Now you may be confusing all these const
s with the constness of the object itself: Just as earlier we had x
and y
, one mutable and one constant, we can apply the same logic to the pointer type itself: Let us define P = T *
, and Q = const T *
. Then the above code, all our objects were mutable objects P p1, p2; Q q1, q2;
. That is, we can always change any of the pointers to point somewhere else: q2 = &z;
. No problem. That's what you're doing to k
when you're reassigning it.
Of course we can also declare constant versions of these pointers: const P p3 = &x; const Q q3 = &y;
. These cannot be reassigned (being constants, after all). If you spell this out in terms of T
, as people usually do, it gets a bit loud:
T * p3 const = &x;
const T * q3 const = &y;
I hope that didn't create more confusion than it resolved!
Upvotes: 2
Reputation: 94299
The issue is that your next
argument is of type const Node *
(read: a pointer to a constant Node object), whereas the Node::next
field is of type Node *
(a pointer to a non-const Node object).
So when doing
tmp->next = next;
You try to make the compiler take a pointer to a const Node
object and assign it to a pointer to a non-const Node
. If this worked, it would be a simple way to circumvent constness, because suddenly people could modify the next
argument simply by dereferencing tmp->next
.
Upvotes: 9