Being a German living in the United Kingdom yields the constant threat of being run over because the cars tend to come from the unexpected direction. Trying to talk sense with the British is entirely futile: “the cars drive on the right side of the street which is the left side!” is a typical response to pointing out that we should globally standardize on just one side. It can reasonably be argued that the majority of the world’s population is actually used to traffic driving on the left side of the street (although I know from experience that this doesn’t necessarily mean a lot in places like India). However, I think it would be reasonable to think about “cars driving on the right side of the street which is the right side of the street”.
What does this have to do with C++? Well, for some unknown reason the predominant placement of placing the const
keyword is UK-style: the right placement for the const
is assumed to be on the left. Note, that if T
is a type the declarations const T
(let’s call this UK-style) and T const
(let’s call this continental-style) are identical. However, UK-style const
placement yields strange inconsistencies. For example, to indicate that a member function isn’t allowed to change the object pointed to by this
you have to place the const
on the right. Also, to deduce the type of a declaration you are best off to read it Arabic-style, i.e. right to left. This makes it easy to determine what is qualified by the const
qualifier: the type to the left of the const
, assuming, of course, that const
is used continental-style. That is, continental-style makes the code not only more consistent but also easier to understand.
If you think that things are easy to understand as they are let’s consider a very simple declaration somewhere in a project:
const int* iterator;
Now, someone get’s the bright idea that it may be reasonable to actually give the type of iterators a name indicating what they are and replacing the above line with the following two lines:
typedef int* int_iterator; const int_iterator iterator;
There was a int*
for which we have a typedef
now so the code got cleaned-up. Makes sense, doesn’t it? Well, maybe not so much: a constant int_iterator
isn’t going to iterate a lot being const
: the semantics of the code changed! That is, the new declaration of iterator
without using a typedef
would look like this:
int* const iterator;
Note that the const
in the above declaration has to be continental-style unless an alias (e.g. a typedef
) for int*
is used. Now, if the original declaration had been continental-style in the first place hopefully nobody would have had the idea of trying to use the typedef
:
int const* iterator;
Somehow, the const
between the int
and the *
should have been a clear sign that the type doesn’t want to be replaced – both to the human reader and the global find-and-replace using something akin to the vi-command %s/int *\*/int_iterator/g
. Well, I know that the vi-command would get this one right but I’m admittedly a bit worried about some human readers, unfortunately especially about those who wouldn’t even dream of using an editor command like the one above.
After having made a hopefully strong case for continental const
placement there is one caveat I shall not leave unmentioned: C has made an attempt to take on some features introduced into C++. One of the features taken up by C as well is const
. From what I can tell, it is an absolute failure. I will back the claim that const
doesn’t quite fit into C up with just one example signature from the standard C library:
char *strchr(const char *s, int c);
Clearly, this signature has got const
-correctness wrong: searching in a string of char const
for a character shouldn’t yield a pointer to a modifiable char
. Please note that I didn’t pick the one error which is considered to be editorial and which should be corrected to become:
const char *strchr(const char *s, int c); // NOT in C
Since functions can’t be overloaded in C and you would want be able to both pass in a constant string but also get out a pointer to a modifiable char
if you passed in a non-const
string in the first place, the signature from the standard C library is intentional and as good as it gets (when include <cstring>
you’ll get properly overloaded, const
-correct version of the function). This is, unfortunately, not the only place where C didn’t get it quite right: aside from the fact you can only declare one level of constness in C (i.e. you can’t have constant pointers, at least not without a typedef
) there is no option for proper const
placement in C at all: the const
qualifier has to be applied UK-style. Ideally, this shouldn’t matter to C++ but in the odd case where you actually have a header file shared between C and C++ this means that you can’t use the correct continental-style const
placement. Fortunately, these headers tend to be written by C programmers and C++ programmers can happily put the const
on the correct side.
IIRC this:
int const * const fred;
is perfectly valid in C, contrary to your last paragraph.
Moreover, const was available since ANSI C, which is a bit pre C++.
This is interesting: I can’t find the quote but I’m sure that in the past people object to continental
const
placement because it can’t be done in C and there are shared headers between C and C++. I quite like this situation because it means that I’m not aware of any technical objection to continentalconst
placement (except, possible for this name).With respect of where
const
was invented, it is certainly true thatconst
appeared in a C standard prior to the C++ standard. However, according e.g. to Bjarne’s FAQ Bjarne claims to have inventedconst
in the context of C++.[…] [If you wonder, why the const is misplaced, read this.] […]
[…] That is, the const needs to go between the type T and the &. The solution would have been obvious if you had put the const into the correct location: const should go to the right. […]