DEV Community

Paul J. Lucas
Paul J. Lucas

Posted on

Symbolic Constant Conundrum

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:

  1. Macros (via #define).
  2. Enumerations.
  3. const.
  4. 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
Enter fullscreen mode Exit fullscreen mode

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 ];
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Conclusion

To summarize:

  • If you’re declaring a set of related, integral constants, use enum (in C) or enum class (in C++).
  • Otherwise, use constexpr if you can.
  • Otherwise, use const.
  • Otherwise, use #define as a last resort.

Top comments (0)