Introduction
A symbolic constant in any programming language is a name — a symbol — that can be used to stand in for a constant — a literal value. Programming languages inherited symbolic constants from mathematics that has many of them grouped by specific field of study. Examples include: π (pi), c (speed of light), e (Euler’s number), G (gravitational constant), h (Plank’s constant), etc. While those exact constants can be defined and used in programs, many programs define program-specific constants. Using constants is better than using magic numbers.
Both C and C++ have acquired multiple ways to specify symbolic constants as their respective languages have evolved over the decades, namely:
- Macros (via
#define). - Enumerations.
-
const. -
constexpr.
Knowing which of the ways to use in a particular case can be quite the conundrum.
Macros
Originally, C only had macros, specifically, object-like macros, e.g.:
#define BUF_SIZE 8192
Macros are adequate, but not good. Why? Macros in general ignore scope, so you typically have to give macros very specific (long) names to avoid collision.
Enumerations
Enumerations in C and C++ are better, especially for declaring a set of related constants. In C++ with enum class, they can even be scoped to avoid collisions; in C, however, they’s still in the global scope.
The other caveat is that they can be constants only for integral values.
const
As I described for C and C++, you can use const for constants, e.g.:
static unsigned const BUF_SIZE = 8192;
char BUF[ BUF_SIZE ];
int main() {
char local_buf[ BUF_SIZE ];
// ...
}
In C++, that will compile just fine without warning; in C, it’ll either be accepted with warnings or rejected entirely, especially if you disable language extensions. Why? Because const is a misnomer since it really means immutable, not constant, and C is more picky about it.
While the declaration of BUF might be accepted, the declaration of local_buf will either be considered a variable length array (VLA) (that, as I pointed out, you should probably never use), or rejected since VLAs are an optional feature and not all compilers support them (notably, Microsoft’s C compiler doesn’t).
A common work-around in C (prior to C23, see below) is to (ab)use enum:
enum {
BUF_SIZE = 8192
};
That is, use a nameless enumeration. The advantage is that enumeration constants really are constant.
constexpr
If you’re using C++11 or later, or C23 or later, there’s constexpr. Unlike const, constexpr really means constant. This is by far the best option for declaring constants:
constexpr unsigned BUF_SIZE = 8192;
Conclusion
To summarize:
- If you’re declaring a set of related, integral constants, use
enum(in C) orenum class(in C++). - Otherwise, use
constexprif you can. - Otherwise, use
const. - Otherwise, use
#defineas a last resort.
























