There are other ways, but you probably don't want to use them. What you're doing is a good use of the preprocessor. Though I'd write something like:
#ifndef MAX_SIZE
#define MAX_SIZE 42
#endif
constexpr size_t maxSize = MAX_SIZE;
That way the actual code part, variable type, name, etc., need only be written once. Also consider:
#indef MAX_SIZE
#error "You need to define MAX_SIZE to compile this code, e.g. -DMAX_SIZE=42"
#endif
That way someone doesn't use the default when they didn't want to, because they didn't know to define it, or something in the build system made the -D flag get lost somewhere.
But there are other ways and one can avoid preprocessor macros!
Generate the source code itself. While this might seem complex, and often is, there are ways to make it less so. Structure the code so that the part which must be generated is small. For instance, derive other values from a single definition of maxSize
instead of generating all the code that needs to know the size. There are also systems that can already do to this in some cases. For example, if one is using CMake, create a header.h.in file like this:
constexpr size_t maxSize = @MAXSIZE@;
And then put this into a CMakeLists.txt file:
set(MAXSIZE 42)
configure_file(header.h.in header.h @ONLY ESCAPE_QUOTES)
When the project is built, cmake will turn header.h.in to header.h with @MAXSIZE@
changed to 42.
This didn't use the C++ preprocessor, but we effectively used the CMake preprocessor to preprocess a file before compiling it, so really, is it any different? It's just a different preprocessor language (which is not nearly as good as the C/C++ preprocessor language).
Another way is with link time constants. A linker symbol is normally the name of a function or global variable. Something with static storage duration. The value of the symbol is the address of the object. But one can, in the commands to the linker, define any symbol you want. Here's an example C file:
#include <stdio.h>
char array1[1];
extern array2[];
int main(void) { printf("%p %p\n", array1, array2); return 0; }
Compile with gcc as gcc example.c -Wl,--defsym=array2=0xf00d
.
Just as it prints the address of array1, it will print 0xf00d
as the address of array2
. And so we have injected a constant into our code without using any preprocessor, neither C's nor CMake's.
But this value is not known to the compiler, only to the linker. It's not an "integer constant expression" and can't be used in certain places, like case labels or the size of an object with static storage duration. Because the compiler needs to know the exact value of those to compile the code. The compiler generated the code for the printf call without knowing the exact value of array1
or array2
. It couldn't do that for a case statement label. This is really while "integer constant expressions" exists in the C/C++ standards, and are not the same as expressions that are constant and have an integer type.