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
constexpr
if you can. - Otherwise, use
const
. - Otherwise, use
#define
as a last resort.