In article <73**********************************@e25g2000prg. googlegroups.com>
<gw****@aol.comwrote:
>[The node-finding] function returns a non-const pointer to a
node of the list. The function itself does not modify the list.
But it returns a pointer through which you could modify the list.
So your function, having promised not to modify the list, can't
then guarantee that the promise is kept. This is why you are
having problems.
Indeed: given a function that does not modify the object or objects
to which its argument pointer(s) point, but *does* return a pointer
to one of those objects, you have a problem.
Do you use "const" to imply "I will not change this, and thus you
can pass to me a pointer to an actually-const, really-in-ROM object",
and then have the return value "de-const-ified"? In which case,
you can do bad things:
typedef <whateverT;
T *deconstify(const T *);
const T read_only_obj = { value_that_cannot_change };
*deconstify(&read_only_obj) = oops_a_bug;
Or, do you avoid const, after which you cannot even *call* the
function even though you would have done it safely? For instance:
T *nonconst(T *); /* nonconst() does not modify it though */
const T *ptr;
ptr = nonconst(&read_only_obj); /* alas, this gets a diagnostic */
The Standard C Library takes the former approach: functions like
strchr() "deconstify" their argument:
const char ro[] = "room!";
*strchr(ro, 'r') = 'b'; /* boom! */
(which may indeed "go boom", i.e., crash, and does on some of my
systems).
One of the many reasons C++ has overloaded functions is to sidestep
this problem. In C++, the types of arguments -- including whether
they are const-qualified -- determine which function is actually
called, so we simply write two completely separate functions:
const int *operate(const int *);
int *operate(int *);
Then:
const int obj1 = 0
int obj2 = 0;
*operate(&obj1) = 42;
will not compile, because &obj1 has type "const int *", so this
calls operate(const int *), which returns "const int *", not
"int *". Of course, this opens the door to abuse: the two
separate functions might well do entirely different things.
Ideally, the const-qualified version of the function does the
same thing as the non-const-qualified version, and in a language
better than C++, we could avoid writing two functions by simply
declaring that the return type matches the argument type:
flexible operate(arg) [
constraint: typeof(operate) is typeof(arg);
constraint: typeof(arg) is choice { int *, const int * };
] {
...
return arg;
}
which also of course lets us write "overloaded" functions without
repeating them six ways from Sunday:
/*
* Substring operation: find "needle" in "haystack"
*/
flexible substring(haystack, needle) [
constraint: typeof(operate) is typeof(haystack);
constraint: typeof(needle) is typeof(haystack);
constraint: typeof(haystack) is choice {
char *, const char *,
Unicode16 *, const Unicode16 *,
Unicode32 *, const Unicode32 *
};
] {
... code to find substring ...
}
(To achieve the above, C++ generally uses "templates" instead of,
or combined with, "overloaded" functions, but there is really
no need for both.)
(I made up the above syntax on the fly, so it is probably full
of flaws.)
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it
http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.