Take this code:
/* somewhere in an external library */
void show_inverted(Image_t bm) {
.....
}
You've just declared show_inverted as taking bm by value. If
show_inverted is going to contain a copy of the original image, then no
problem occurs if it modifies that copy, and there is therefore no
reason why you need to use const.
If, on the other hand, "Image_t" is a typedef for a pointer, such as
"struct Image*", then this provides a prime example why it's a bad idea
to define such typedefs. What you need, in order to properly protect
certain pieces of code, is the ability to declare some pointers as
"const struct Image*", and you can't construct such a type using that
typedef. "const Image_t" is equivalent to "Image_t const" - both mean
"struct Image * const", which is a very different type from "struct
Image const *". "struct Image * const" means that the pointer value
should not be written to, whereas "struct Image const*" means the same
thing as "const struct Image*", which is that the object pointed at
should not be written to.
/* In your code */
Image_t img;
.... hundreds of lines later...
show_inverted(img);
But you understand that either (1) show_inverted() requires the image to be
writeable, but is unchanged at the end; or (2) show_inverted() will corrupt
the image. How best to make use of 'const' here?
Maybe, you change all these calls (if you have many dozens of them scattered
everywhere) so that they look like this:
show_inverted((const Image_t)img);
But a few problems with this:
(0) It has no useful effect.
It's possible to declare objects as 'const' in themselves, but only if
they never need to be modified after initialization, which is fairly
unusual, especially for large objects, (such as those that might contain
an entire image). More commonly, 'const' is used in 'const T*', to mark
the object pointed at as something that should not be modified. A
pointer to a non-const object will end up being converted to "const T*",
but almost never by explicit conversion. It usually is done by passing a
"T*" argument to a function declared as taking a "const T*" parameter -
precisely the situation you complained about being allowed in an earlier
message. Allowing such implicit conversions is a key part of what makes
'const' useful - saying that the reverse conversion can NOT occur
implicitly is another key part.
It's also feasible to do such an implicit conversion without a function
call:
struct Image img;
// code that fills in img
const struct Image *cpimg = &img;
// Code that uses cpimg to do thing that should not modify 'img'.
Within the scope of a "const T*" declaration, the 'const' ensures that
attempts to modify the pointed-at object through that pointer will be
constraint violations, guaranteeing that you will be warned of the
necessity of re-writing your code to avoid that problem. Such code is
problematic, whether or not 'const' is used. 'const' serves only to
enable warnings about such code.