In C, C++, and D, all data types, including those defined by the user, can be declared const, and const-correctness dictates that all variables or objects should be declared as such unless they need to be modified. Such proactive use of const makes values "easier to understand, track, and reason about", and it thus increases the readability and comprehensibility of code and makes working in teams and maintaining code simpler because it communicates information about a value's intended use. This can help the
compiler as well as the developer when reasoning about code. It can also enable an
optimizing compiler to generate more efficient code.
Simple data types For simple non-pointer data types, applying the const qualifier is straightforward. It can go on either side of some types for historical reasons (for example, const char foo = 'a'; is equivalent to char const foo = 'a';). On some implementations, using const twice (for instance, const char const or char const const) generates a warning but not an error.
Pointers and references For pointer and reference types, the meaning of const is more complicated – either the pointer itself, or the value being pointed to, or both, can be const. Further, the syntax can be confusing. A pointer can be declared as a const pointer to writable value, or a writable pointer to a const value, or const pointer to const value. A const pointer cannot be reassigned to point to a different object from the one it is initially assigned, but it can be used to modify the value that it points to (called the
pointee). Reference variables in C++ are an alternate syntax for const pointers. A pointer to a const object, on the other hand, can be reassigned to point to another memory location (which should be an object of the same type or of a convertible type), but it cannot be used to modify the memory that it is pointing to. A const pointer to a const object can also be declared and can neither be used to modify the pointee nor be reassigned to point to another object. The following code illustrates these subtleties: void foo(int* p, const int* cp, int* const pc, const int* const cpc) { *p = 0; // OK: modifies the pointed to data p = NULL; // OK: modifies the pointer *cp = 0; // Error! Cannot modify the pointed to data cp = NULL; // OK: modifies the pointer *pc = 0; // OK: modifies the pointed to data pc = NULL; // Error! Cannot modify the pointer *cpc = 0; // Error! Cannot modify the pointed to data cpc = NULL; // Error! Cannot modify the pointer }
C convention Following usual C convention for declarations, declaration follows use, and the * in a pointer is written on the pointer, indicating
dereferencing. For example, in the declaration int *p, the dereferenced form *p is an int, while the reference form p is a pointer to an int. Thus const modifies the
name to its right. The C++ convention is instead to associate the * with the type, as in int* p, and read the const as modifying the
type to the left. const int * cp can thus be read as "*cp is a const int" (the value is constant), or "cp is a const int *" (the pointer is a pointer to a constant integer). Thus: int* p; // *p is an int value const int* cp; // *cp is a constant int int* const pc; // pc is a constant int* const int* const cpc; // cpc is a constant pointer and points to a constant value
C++ convention Following C++ convention of analyzing the type, not the value, a
rule of thumb is to read the declaration from right to left. Thus, everything to the left of the star can be identified as the pointed type and everything to the right of the star are the pointer properties. For instance, in our example above, const int* can be read as a writable pointer that refers to a non-writable integer, and int* const can be read as a non-writable pointer that refers to a writable integer. A more generic rule to aid in understanding complex declarations and definitions works like this: • find the identifier of the declaration in question • read as far as possible to the right (i.e., until the end of the declaration or to the next closing parenthesis, whichever comes first) • back up to where reading began, and read backwards to the left (i.e., until the beginning of the declaration or to the open-parenthesis matching the closing parenthesis found in the previous step) • once reaching the beginning of the declaration, finish. If not, continue at step 2, beyond the closing parenthesis that was matched last. Here is an example: When reading to the left, it is important to read the elements from right to left. So an const int* becomes a
pointer to a const int and not a
const pointer to an int. In some cases C/C++ allows the const keyword to be placed to the right of the type. Here are some examples: const int* cp; // equivalent to int const* cp, const int* const cpc; // equivalent to int const* const cpc Although C/C++ allows such definitions (which closely match the English language when reading the definitions from left to right), the compiler still reads the definitions according to the abovementioned procedure: from right to left. But putting const
before what must be constant quickly introduces mismatches between what is intended to be written and what the compiler decides was written. Consider pointers to pointers: int** pp; // a pointer to a pointer to ints const int** cpp; // a pointer to a pointer to constant int value (not a pointer to a constant pointer to ints) int* const* pcp; // a pointer to a const pointer to int values (not a constant pointer to a pointer to ints) int** const ppc; // a constant pointer to pointers to ints (ppc, the identifier, being const makes no sense) const int** const cppc; // a constant pointer to pointers to constant int values Some choose to write the pointer symbol on the variable, reasoning that attaching it to the type is potentially confusing, as it strongly suggests a pointer "type" which is not necessarily the case in C. // two ways: int* a; // left-aligned asterisk int *a; // right-aligned asterisk int* a, b; // confusing (a is a pointer to an int but b is merely an int) int *a, b; // a is a pointer to an int and b is an int int* a, *b; // ugly (a and b are both pointers to ints, but this is an awkward way to write) int *a, *b; Note that in
C#, right-alignment of the asterisk is illegal. int *a, *b; // illegal in C# int* a, b; // declares two int* Bjarne Stroustrup's FAQ recommends only declaring one variable per line if using the C++ convention, to avoid this issue. The same considerations apply to defining references and rvalue references: int i = 22; const int& cr = i; const int& cr2 = i, cr3 = i; // confusing: // cr2 is a reference, but cr3 isn't: // cr3 is a constant int initialized with i's value // error: as references can't change anyway. int& const rc = myInt; // C++: int&& rref = int(5), value = 10; // confusing: rref is an rvalue reference, but value is a mere int. int &&rref = int(5), value = 10; More complicated declarations are encountered when using multidimensional arrays and references (or pointers) to pointers. Although it is sometimes argued that such declarations are confusing and error-prone and that they therefore should be avoided or be replaced by higher-level structures, the procedure described at the top of this section can always be used without introducing ambiguities or confusion.
Parameters and variables const can be declared both on function parameters and on variables (
static or automatic, including global or local). The interpretation varies between uses. A const static variable (
global variable or static
local variable) is a constant, and may be used for data like mathematical constants, such as const double PI = 3.14159 – realistically longer, or overall compile-time parameters. A const automatic variable (non-static local variable) means that
single assignment is happening, though a different value may be used each time, such as const int x_squared = x * x. A const parameter in pass-by-reference means that the referenced value is not modified – it is part of the
contract – while a const parameter in pass-by-value (or the pointer itself, in pass-by-reference) does not add anything to the interface (as the value has been copied), but indicates that internally, the function does not modify the local copy of the parameter (it is a single assignment). For this reason, some favor using const in parameters only for pass-by-reference, where it changes the contract, but not for pass-by-value, where it exposes the implementation. == C++ ==