For example, let's consider the static
storage class specifier. Here are a few examples of both valid and ill-formed uses of this storage class specifier:
static int a; // valid
int static b; // valid
static int* c; // valid
int static* d; // valid
int* static e; // ill-formed
static int const* f; // valid
int static const* g; // valid
int const static* h; // valid
int const* static i; // ill-formed
typedef int* pointer;
static pointer j; // valid
pointer static k; // valid
(The declarations marked "valid" were accepted by Visual C++ 2012, g++ 4.7.2, and Clang++ 3.1. The declarations marked "ill-formed" were rejected by all of those compilers.)
This seems odd because the storage class specifier applies to the declared variable. It is the declared variable that is static
, not the type of the declared variable. Why are e
and i
ill-formed, but k
is well-formed?
What are the rules that govern valid placement of storage class specifiers? While I've used static
in this example, the question applies to all storage class specifiers. Preferably, a complete answer should cite relevant sections of the C++11 language standard and explain them.
If you employ the "Golden Rule" (which also doesn't apply only to pointers) it follows naturally, intuitively, and it avoids a lot of mistakes and pitfalls when declaring variables in C/C++. The "Golden Rule" should not be violated (there are rare exceptions, like
const
applied to array typedefs, which propagatesconst
to the base type, and references, that came with C++).K&R, Appendix A, Section 8.4, Meaning of Declarators states:
To declare a variable in C/C++ you should really think of the expression you should apply to it to get the base type.
1) There should be a variable name
2) Then comes the expression as valid* out of the declaration statement, applied to the variable name
3) Then comes the remaining information and properties of declaration like base type and storage
Storage is not a characteristic you can always confer to the outcome of expressions, contrary to constness for example. It makes sense only at declaration. So storage must come somewhere else that's not in 2.
I think K&R wanted us to use inverted reasoning when declaring variables, it's frequently not the common habit. When used, it avoids most of complex declaration mistakes and difficulties.
*valid is not in a strict sense, as some variations occur, like x[], x[size, not indexing], constness, etc... So 2 is a expression that maps well (for the declaration usage), "same form", one that reflects variable's use, but not strictly.
Golden Rule Bonus for the Uninitiated
In the context of declarations,
&
is not an operation to get an address, it just tells what's a reference.f()
:f
is a function&
return: its return is a reference[3]
: the reference is to an array of 3 elementsint
array[i]: an element is an intSo you have a function that returns a reference to an array of 3 integers, and as we have the proper compile time information of the array size, we can check it with
sizeof
anytime =)Final golden tip, for anything that can be placed before the type, when in multiple declarations, it's to be applied to all the variables at once, and so can't be applied individually.
This
const
can't be put beforeint
:So the following is valid:
This one can:
So the following is invalid:
The exchangeable
const
is to be applied for all:Declaration Conventions
Because of that, I always put everything that can't be put before the type, closer to the variable (
int *a
,int &b
), and anything that can be put before, I put before (volatile int c
).There's much more on this topic at http://nosubstance.me/post/constant-bikeshedding/.