Const correctness in C vs C++ - c++

I understand what const correctness means and my question is not about what const correctness is. So I am not expecting an explanation or C++-FAQ links for that.
My questions are:
What are the semantic differences between const in C and const in C++? and
What is the reason for the difference?
Quotes from the respective standards which make the differences clear would be nice to have.
I regularly switch between C and C++ and I would like to know the important points that one should keep in mind while doing so.
I don't seem to remember the reason for these (special thanks if you can provide a reasoning) but from the top of my mind, I can remember:
const variables in C++ have internal linkage by default, while in C they have default external linkage;
const objects can be used as compile-time values in C++, but cannot be used as compile-time values in C;
Pointers to string literals must be an char const* in C++ but in C it can be char*.
What am I missing?

In addition to the differences you cite, and the library differences that
Steve Jessop mentions,
char* p1;
char const* const* p2 = &p1;
is legal in C++, but not in C. Historically, this is because C
originally allowed:
char* p1;
char const** p2 = &p1;
Shortly before the standard was adopted, someone realized that this
punched a hole in const safety (since *p2 can now be assigned a
char const*, which results in p1 being assigned a char const*); with
no real time to analyse the problem in depth, the C committee banned any
additional const other than top level const. (I.e. &p1 can be
assigned to a char ** or a char **const, but not to a char const**
nor a char const* const*.) The C++ committee did the further
analysis, realized that the problem was only present when a const
level was followed by a non-const level, and worked out the necessary
wording. (See §4.4/4 in the standard.)

In C const declarations do not produce constant expressions, i.e. in C you can't use a const int object in a case label, as a bit-field width or as array size in a non-VLA array declaration (all this is possible in C++). Also, const objects have external linkage by default in C (internal linkage in C++).
Const-correctness rules of C++ language support the following standard conversion
int **pp = 0;
const int *const *cpp = pp; // OK in C++
int ***ppp = 0;
int *const *const *cppp = ppp; // OK in C++
These will not work in c.

The reason for some of these differences is to allow us to get rid of preprocessor macros, which was one of Bjarne's early design goals.
In C we might have
#define MAX_FOOS 10
int foos[MAX_FOOS];
In C++ we'd prefer to be able to write
const int max_foos = 10;
int foos[max_foos];
For that to work max_foos needs to be usable in a constant expression. It also needs to have internal linkage, so the definition can appear in a header without causing multiple definition errors, and more importantly to make it easier for the compiler to not allocate any storage for max_foos.
When the C committee adopted const from C++ they didn't adopt the antipathy to macros, so they didn't need these semantics.

Related

Is it safe to "play" with parameter constness in extern "C" declarations?

Suppose I'm using some C library which has a function:
int foo(char* str);
and I know for a fact that foo() does not modify the memory pointed to by str. It's just poorly written and doesn't bother to declare str being constant.
Now, in my C++ code, I currently have:
extern "C" int foo(char* str);
and I use it like so:
foo(const_cast<char*>("Hello world"));
My question: Is it safe - in principle, from a language-lawyering perspective, and in practice - for me to write:
extern "C" int foo(const char* str);
and skip the const_cast'ing?
If it is not safe, please explain why.
Note: I am specifically interested in the case of C++98 code (yes, woe is me), so if you're assuming a later version of the language standard, please say so.
Is it safe for me to write: and skip the const_cast'ing?
No.
If it is not safe, please explain why.
-- From language side:
After reading the dcl.link I think exactly how the interoperability works between C and C++ is not exactly specified, with many "no diagnostic required" cases. The most important part is:
Two declarations for a function with C language linkage with the same function name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same function.
Because they refer to the same function, I believe a sane assumption would be that the declaration of a identifier with C language linkage on C++ side has to be compatible with the declaration of that symbol on C side. In C++ there is no concept of "compatible types", in C++ two declarations have to be identical (after transformations), making the restriction actually more strict.
From C++ side, we read c++draft basic#link-11:
After all adjustments of types (during which typedefs are replaced by their definitions), the types specified by all declarations referring to a given variable or function shall be identical, [...]
Because the declaration int foo(const char *str) with C language linkage in a C++ translation unit is not identical to the declaration int foo(char *str) declared in C translation unit (thus it has C language linkage), the behavior is undefined (with famous "no diagnostic required").
From C side (I think this is not even needed - the C++ side is enough to make the program have undefined behavior. anyway), the most important part would be C99 6.7.5.3p15:
For two function types to be compatible, both shall specify compatible return types. Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types [...]
Because from C99 6.7.5.1p2:
For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.
and C99 6.7.3p9:
For two qualified types to be compatible, both shall have the identically qualified version of a compatible type [...]
So because char is not compatible with const char, thus const char * is not compatible with char *, thus int foo(const char *) is not compatible with int foo(char*). Calling such a function (C99 6.5.2.2p9) would be undefined behavior (you may see also C99 J.2)
-- From practical side:
I do not believe will be able to find a compiler+architecture combination where one translation unit sees int foo(const char *) and the other translation unit defines a function int foo(char *) { /* some stuff */ } and it would "not work".
Theoretically, an insane implementation may use a different register to pass a const char* argument and a different one to pass a char* argument, which I hope would be well documented in that insane architecture ABI and compiler. If that's so, wrong registers will be used for parameters, it will "not work".
Still, using a simple wrapper costs nothing:
static inline int foo2(const char *var) {
return foo(static_cast<char*>(var));
}
I think the base answer is:
Yes, you can cast off const even if the referenced object is itself const such as a string literal in the example.
Undefined behaviour is only specified to arise in the event of an attempt to modify the const object not as a result of the cast.
Those rules and their reason to exist is 'old'. I'm sure they predate C++98.
Contrast it with volatile where any attempt to access a volatile object through a non-volatile reference is undefined behaviour. I can only read 'access' as read and/or write here.
I won't repeat the other suggestions but here is the most paranoid solution.
It's paranoid not because the C++ semantics aren't clear. They are clear. At least if you accept something being undefined behaviour is clear!
But you've described it as 'poorly written' and you want to put some sandbags round it!
The paranoid solution relies on the fact that if you are passing a constant object it will be constant for the whole execution (if the program doesn't risk UB).
So make a single copy of "hello world" lower in the call-stack or even initialised as a file scope object. You can declare it static in a function and it will (with minimal overhead) only be constructed once.
This recovers almost all of the benefits of string literal. The lower down the call stack including file-scope (global you put it the better.
I don't know how long the lifetime of the pointed-to object passed to foo() needs to be.
So it needs to be at least low enough in the chain to satisfy that condition.
NB: C++98 has std::string but it won't quite do here because you're still forbidden for modifying the result of c_str().
Here the semantics are defined.
#include <cstring>
#include <iostream>
class pseudo_const{
public:
pseudo_const(const char*const cstr): str(NULL){
const size_t sz=strlen(cstr)+1;
str=new char[sz];
memcpy(str,cstr,sz);
}
//Returns a pointer to a life-time permanent copy of
//the string passed to the constructor.
//Modifying the string through this value will be reflected in all
// subsequent calls.
char* get_constlike() const {
return str;
}
~pseudo_const(){
delete [] str;
}
private:
char* str;
};
const pseudo_const str("hello world");
int main() {
std::cout << str.get_constlike() << std::endl;
return 0;
}

What syntax rules explain how C++ compilers process the const keyword in variable declarations [duplicate]

This question already has answers here:
What is the difference between const int*, const int * const, and int const *?
(23 answers)
Closed 8 years ago.
char * const a;
const char * a;
One is a constant pointer to a char. The other is a pointer to a char const.
I think there is a specific way. Something like order of execution. I think there should be brackets around or something.
Can anyone explain to me why the pointer is const on one and not the other?
char * const a, for example.
Does that become (char *) const a
Or
const char * a
Is it (const (char *)) a
In What is the difference between const int*, const int * const, and int const *? I got a bunch of explanations that do not seem to make sense.
One explanation is to read it backward.
Another explanation is to split the * and see if the const is on the right or left.
Then there is another bizare theory of doing it clockwise
http://c-faq.com/decl/spiral.anderson.html
The rules do not seem like the real actual formal rule. I am not even sure if the rule holds for even more complex types. Not to mention the rules only work in english.
Okay, what's the actual formal rule here and what's the reference?
I think there has to be a simpler rule on why this is so.
For example, does the word const apply to the left or right? Can we put brackets like I suggest?
The rules are in the C++ standard. You can download free drafts from links here.
Appendices A.6 and A.7 have the full grammar specification. The description of declarators is in chapter [dcl]. See section [dcl.ptr] in particular, e.g. #1:
In a declaration T D where D has the form
* attribute-specifier-seqopt cv-qualifier-seqopt D1
and the type of the identifier in the declaration T D1 is “derived-declarator-type-list T,” then the type of the identifier of D is “derived-declarator-type-list cv-qualifier-seq pointer to T.” The cv-qualifiers apply to the pointer and not to the object pointed to. Similarly, the optional attribute-specifier-seq (7.6.1) appertains to the pointer and not to the object pointed to.
The rest of that section contains a bunch of clarifying examples.
The rule is very simple, if the const is on the right-hand-side of the *: it's the pointer that's const. If the const is on the left-hand-side of the * then the object that's being pointed to is const.
A pragmatic online reference of the syntax for const pointer declarations saves you downloading some 1400 pages of a draft version of any C++ Standard (a SO-community-maintained list).
The formal grammar is covered in Appendix A of the book The C++ Programming Language by Bjarne Stroustrup ISBN 0-321-563840 (every former edition will do, too). In chapter 5.4.1 (of the 3rd edition), Bjarne Stroustrup describes the reason behind this syntax diversity as follows:
The declarator operator that makes a pointer constant is *const. There is no const* declarator operator, so a const appearing before the * is taken to be part of the base type.
Ask your C++ compiler!
It's most likely that compiler implementers don't like[1] this "feature": if you try the following declaration,
const char const * const a;
you'll see that there are special rules implemented in your C++ compiler, and it will probably tell you about it. The GNU GCC compiler for example states
error: duplicate 'const'
[1] ...but this anger seems negligible compared with that created by unsigned long literals...

Does the position of the * or & matters? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicates:
In C, why is the asterisk before the variable name, rather than after the type?
What's your preferred pointer declaration style, and why?
In C++, i see pointers put in different ways. for example,
char* ptr
char * ptr
char *ptr
Are the 3 examples above identical? (same goes with the &)
Doesn't matter. (Eg. they are the same.) You could even write char*ptr; without any whitespace.
Beware though with multiple declarations on one line: char* ptr, noptr;. Here, the third syntax comes in clearer: char *ptr, noptr;.
My rule to avoid that confusion: Only one variable per line. Another way to do it right without the possibility to miss a *: typedef the pointer, eg:
typedef char* CharPtr;
CharPtr ptr1, ptr2; // now both are pointer
Though then you need to be aware of other things, like constness, so I stick to my rule mentioned above:
typedef char* CharPtr;
const CharPtr p1; // const char* ?? or char* const ??
CharPtr const p2; // char* const ?? or const char* ??
// Eg.: Can't the pointer or the pointee be changed?
(And all of the above also applies to references &.)
C people tend to prefer char *p, whereas C++ people tend to prefer char* p (at least Bjarne does). The compiler could not care less about whitespace, you could just as well say char * p or char*p.
Many people say that char* p is dangerous because char* p, q is potentially confusing. That is correct in principle, but:
To increase readability, you should not declare multiple names in one declaration, anyway.
And more importantly, in C++, you should generally prefer RAII types over raw pointers.
C++ people emphasize the types, and C people emphasize the usage.
So you see char* x in C++, it is because "the type of x is pointer-to-char". C++ has a strong type system on which the language rests. This is not the case in C.
In C, you declare variables according to what you want to do with them. When you see char *x in C, the thought process is "when you dereference x, you get a char". Another example:
char (*f)(int);
reads "when you dereference f and call it with a int, you get a char", ie. f is a pointer to a function which takes int and returns char.
They all work. char *ptr is often advocated because it makes it clearer what is happening in this case:
char *ptr, var;
This declares a char pointer and a char.
It does not matter. However, C++ people tend to prefer char* ptr, while C people prefer char *ptr. It's all a matter of personal preference.
However, note that char* ptr, noptr declares ptr as a pointer and noptr as a char value.
The potential whitespace surrounding the * and & modifiers in declaration have no effect on the compiled code.
C++ is a free-form language. So, space doesn't really count in this case and all mean the same.

Is there const in C?

This question may be naive, but:
is there const keyword in C?
since which version?
are there any semantic and/or syntactic differences between const in C and C++?
There are no syntactic differences between C and C++ with regard to const keyword, besides a rather obscure one: in C (since C99) you can declare function parameters as
void foo(int a[const]);
which is equivalent to
void foo(int *const a);
declaration. C++ does not support such syntax.
Semantic differences exist as well. As #Ben Voigt already noted, in C const declarations do not produce constant expressions, i.e. in C you can't use a const int object in a case label, as a bit-field width or as array size in a non-VLA array declaration (all this is possible in C++). Also, const objects have external linkage by default in C (internal linkage in C++).
There's at least one more semantic difference, which Ben did not mention. Const-correctness rules of C++ language support the following standard conversion
int **pp = 0;
const int *const *cpp = pp; // OK in C++
int ***ppp = 0;
int *const *const *cppp = ppp; // OK in C++
These initializations are illegal in C.
int **pp = 0;
const int *const *cpp = pp; /* ERROR in C */
int ***ppp = 0;
int *const *const *cppp = ppp; /* ERROR in C */
Generally, when dealing with multi-level pointers, C++ says that you can add const-qualification at any depth of indirection, as long as you also add const-qualification all the way to the top level.
In C you can only add const-qualification to the type pointed by the top-level pointer, but no deeper.
int **pp = 0;
int *const *cpp = pp; /* OK in C */
int ***ppp = 0;
int **const *cppp = ppp; /* OK in C */
Another manifestation of the same underlying general principle is the way const-correctness rules work with arrays in C and C++. In C++ you can do
int a[10];
const int (*p)[10] = &a; // OK in C++
Trying to do the same in C will result in an error
int a[10];
const int (*p)[10] = &a; /* ERROR in C */
The first two questions are answered here: Const in C
Yes there are quite a few differences in semantics between const in C and C++.
In C++, const variables of appropriate type are integral constant expressions (if their initializers are compile-time constant expressions) and can be used in context which requires that, such as array bounds, and in enum definitions. In C, they are not and cannot be.
In C++, const global variables automatically have static linkage, so you can put them in header files. In C, such variables have external linkage and that would generate duplicate definition errors at link time.
Yes, there is a const keyword. It was added as part of the 1989 standard.
As far as compatibility, here's a paragraph from Harbison & Steele, 5th edition:
A top-level declaration that has the type qualifier const but no explicit storage class is considered to be static in C++ but extern in C. To remain compatible, examine top-level const declarations and provide an explicit storage class.
In C++, string constants are implicitly const; they are not in C.
Yes, const has been there since at least since ANSI C (aka C89).
It certainly appears in my copy of "The C Programming Language (2nd Edition)", Kernighan & Ritchie (published in 1988).
Relevant extract:
The const and volatile properties are new with the ANSI standard. The purpose of const is to
announce objects that may be placed in read-only memory, and perhaps to increase opportunities for
optimization.
Two other differences:
const arraytype (i.e typedef int A[1]; const A a = { 0 };) specifies a constant array type ( http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#112 and http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1059 ) (and whose elements are so-qualified too) in C++, but a non-constant array type whose elements are so-qualified in C.
const const is valid in C99 (not valid in C89), not valid in C++ in any version (you can only semantically repeat a const, not syntactically). It contracts to const in C99.
Yes. const is there in C, from C89.
Here is a good read is about behaviour of const keyword in C.
The semantic in C is different than in C++. For example,
unsigned const a = 10;
unsigned A[a];
in file scope would be valid in C++, but not in C.
Yes, there's a const keyword in C. It's been there since C90.
Syntactically, it can occur in the same places as in C++. Semantically, it's a bit more lax, IIRC.
According to ESR, const was added in the ANSI C Draft Proposed Standard. Eric Giguere's summary of ANSI C, dated 1987, confirms it.
This looks like the draft itself -- search for "3.5.3 Type qualifiers".
There is a "const" keyword in C, and it has been for a long time. If a variable is designated "const", writes to it are forbidden.
Additionally, in some environments, variables declared "const" may be located in a different data segment from other variables. This data segment may offer hardware write protection, and for embedded systems, may be stored in ROM or flash memory rather than in RAM (a very important distinction on some processors which have a lot more ROM or flash than RAM--e.g. 128 KB flash and 3.5 KB RAM, or 2 KB ROM and 96 bytes RAM).
Note that the compiler will generally not make any inferences about "const" values or expressions involving them. If I say "const char foo[] = "Hello";" and then later make reference to foo[1], the compiler will load the value (which will most likely be 'e') from wherever foo[] is stored and use the loaded value. Sometimes this usefully allows values to be patched in a compiled code image, but sometimes it just wastes code.
If you want to define a number to to be a compile-time "substitutable" constant, the best way, at least for integer constants, may be to use "enum". For example, "enum {woozle=19;}" will cause 19 to be substituted for "woozle" throughout the code. Note that unlike textual substitutions; enum declarations obey proper rules of scope.

const int *p vs. int const *p - Is const after the type acceptable?

My co-worker is 0 for 2 on questions he has inspired (1, 2), so I thought I'd give him a chance to catch up.
Our latest disagreement is over the style issue of where to put "const" on declarations.
He is of the opinion that it should go either in front of the type, or after the pointer. The reasoning is that this is what is typically done by everyone else, and other styles are liable to be confusing. Thus a pointer to a constant int, and a constant pointer to int would be respectively:
const int *i;
int * const i;
However, I'm confused anyway. I need rules that are consistent and easy to understand, and the only way I can make sense of "const" is that it goes after the thing it is modifying. There's an exception that allows it to go in front of the final type, but that's an exception, so it's easier on me if I don't use it.
Thus a pointer to a constant int, and a constant pointer to int would be respectively:
int const * i;
int * const i;
As an added benefit, doing things this way makes deeper levels of indirection easier to understand. For example, a pointer to a constant pointer to int would clearly be:
int * const * i;
My contention is that if someone just learns it his way, they'll have little trouble figuring out what the above works out to.
The ultimate issue here is that he thinks that putting const after int is so unspeakably ugly, and so harmful to readability that it should be banned in the style guide. Of course, I think if anything the guide should suggest doing it my way, but either way we shouldn't be banning one approach.
Edit:
I've gotten a lot of good answers, but none really directly address my last paragraph ("The ultimate issue"). A lot of people argue for consistency, but is that so desirable in this case that it is a good idea to ban the other way of doing it, rather that just discouraging it?
The most important thing is consistency. If there aren't any coding guidelines for this, then pick one and stick with it. But, if your team already has a de facto standard, don't change it!
That said, I think by far the more common is
const int * i;
int * const j;
because most people write
const int n;
instead of
int const n;
A side note -- an easy way to read pointer constness is to read the declaration starting at the right.
const int * i; // pointer to an int that is const
int * const j; // constant pointer to a (non-const) int
int const * aLessPopularWay; // pointer to a const int
There's a class of examples where putting the const on the right of the type also helps avoid confusion.
If you have a pointer type in a typedef, then it is not possible to change the constness of the to type:
typedef int * PINT;
const PINT pi;
pi still has the type int * const, and this is the same no matter where you write the const.
I hope this explanation from B. Stroustrup's FAQ on Style & Techniques will give you a definite answer.
Bjarne Stroustrup's C++ Style and Technique FAQ
I personaly prefer:
int const* pi;
int* const pi;
Because const identifies the left token which is intended to be const.
And you definitely keep the same consistency when using smth like that:
int const* const pi;
Instead of writing inconsistently:
const int* const pi;
And what happens if you have a pointer to pointer and so on:
int const* const* const pi;
Instead of:
const int* const* const pi;
I was at a conference where Bjarne Stroustrup was giving a presentation, and he used something like const int* i. Someone asked him why does he use this style and he responded (paraphrasing):
"people like to see const first when something is constant."
People typically use const int* blah because it reads well as English. I wouldn't underestimate the usefulness of that.
I find that the int* const blah variation is rare enough that it's not typically useful to make the more common definition backwards. I am, in general, not a fan of anything that even slightly obscures code in the general case, though it might provide some nominal benefit in the exceptional case.
See also "if (1 == a)". Some people really enjoy writing code that doesn't read as English. I am not one of those people.
Really, though, the rules behind const are simple. Look to the left, then to the right. So simple that I wouldn't think it's worth much attention in a style guide.
If it were only variables and pointers to them that could be const or not, it would probably not matter that much. But consider:
class MyClass
{
public:
int foo() const;
};
No way that const could be written anywhere else but trailing the function it refers to.
The shorter a style guide, the more likely developers will follow it. And the shortest rule possible, and the only rule that will give you consistency, is:
The const keyword always trails whatever it is referring to.
So, I'd say 0:3 for your coworker here.
Regarding your "ultimate issue": For the sake of the style guide, it does not matter whether the guide "discourages" or "bans" the things it speaks out against. That is a social issue, a policy. The style guide itself should be as crisp and short as possible. Long style guides just get ignored by everybody (except management on the lookout for someone to blame), so just write "do" or "don't", and state what you do with violations of the guide elsewhere (e.g. in the company policy of how peer reviews are being done).
While there is no meaningful difference between const int and int const (and I've seen both styles in use), there is a difference between const int * and int * const.
In the first, you have a pointer to a const int. You can change the pointer, but you can't change the value it points to. In the second, you have a const pointer to int. You can't change the pointer (hope it's initialized to your liking), but you can change the value of the pointed-to int.
The proper comparison is with const int * and int const *, which both are pointers to a const int.
Remember that the * doesn't necessarily work as you might like. The declaration int x, y; will work as you expect, but int* x, y; declares one pointer to int, and one int.
Putting "const" after the type declaration makes a whole lot more sense once you train yourself to read your C++ type declarations from right to left.
I'm going to peg your cow-orker at 0-for-3 :)
Personally I (and it is a personal preeference) I finding reading type declarations from right to left the easiest. Especially when you start throwing references into the mix.
std::string const& name = plop; // reference to const string.
const std::string& flame =plop; // reference to string const;
// That works better if you are German I suppose :-)
The ultimate issue here is that he thinks that putting const after int is so unspeakably ugly, and so harmful to readability that it should be banned in the style guide
Really?
Show me a programmer who gets bogged down when he sees:
int foo() {
}
vs
int foo()
{
}
...and I'll show you a programmer who doesn't pay close enough attention to detail.
No professional programmer worth his salt will have a problem with superficial differences in style.
EDIT: It is true that const int* and int* const don't mean exactly the same thing, but that wasn't the point. The point made by OP's coworker was that differences in style make code difficult to understand & maintain. It is this claim I disagree with.
Let me put my 2¢ into the discussion.
In modern C++ I'd recommend to stick to int const instead of const int to highlight the difference between const and constexpr. It would help programmer to remember that const int cannot be always thought as compile-time constant, because:
compiler may (will?) allocate memory for it,
this memory may even be not write-protected (stack segment),
such a constant cannot serve all the duties constexpr can.
Rules are good to follow. Simpler rules are better.
Const goes to the right of what's const.
Take this declaration:
int
main
( int const argc
, char const * const * const argv
)
...
I agree with both of you. You should put the const after the type. I also find looking at it an abomination that must be destroyed. But my recent foray into the wonders of const value parameters has made me understand why putting the const second makes sense.
int *
int const *
int * const
int const * const
Just looking at that has the hairs on my neck standing. I'm sure it would confuse my co-workers.
EDIT: I was just wondering about using this in classes:
class Foo {
Bar* const bar;
Foo(const Foo&) = delete; // would cause too many headaches
public:
Foo() : bar(new Bar) {}
~Foo() { delete bar; }
};
bar in this example is functionally equivalent to Bar& but it is on the heap and can be deleted. For the lifetime of each Foo, there will be a single Bar associated with it.
I like to to use the following form for declaring "manifest constants". In this case, the value itself is a constant so I put the "const" first (same as Bjarne) to emphasize that the constness should be manifest at compile-time, and usable as such for specific optimizations by the compiler.
const int i = 123;
For declaring references which will not be used to modify the value, I use the following form which emphasizes the fact that the identifier is a "constant reference". The referenced value may or may not be a constant. [Related discussion: Would you even be using "const" for function parameters if they were not pointers or references? Use of 'const' for function parameters]
void fn( int const & i );
For pointers, I use the same form that I use for references, for essentially the same reason (although the term "constant pointer" seems a little more ambiguous than "constant reference").
void fn( int const * i );
Also, as another poster noted, this form remains consistent when you have multiple levels of indirection.
void fn( int const * const * i );
The final scenario, where you are declaring a pointer which is constant is pretty rare in my experience with C++. In any case, you don't really have any choices here. [This case demonstrates that the most consistent approach would be to put the word "const" after the type -- since that is, in fact, required for this particular declaration.]
void fn( int * const i );
...unless you use a typedef.
typedef int * IntPtr;
void fn1( const IntPtr i );
void fn2( IntPtr const i );
One final note: Unless you are working in a low-level domain, most C++ code should never declare a pointer. Therefore, that aspect of this discussion is probably more relevant to C.
In modern C++11, you could also use template typedefs to add clarity to complicated declarations:
template<typename T>
using Const = const T;
template<typename T>
using Ptr = T*;
The three declarations you mentioned in your question:
int const * a;
int * const b;
int * const * c;
would then be written as:
Ptr<Const<int>> a;
Const<Ptr<int>> b;
Ptr<Const<Ptr<int>>> c;