I am writing a library that needs to do some compile time calculations, and builds an array of compile time constants. The issue is I need a way to specify the max size of this array... The only way I know of is to make it a configurable option passed to the compiler.
Then you can use it with preprocessor directives for example:
#ifndef MAX_SIZE
constexpr auto maxSize = 42; // Some default value if no MAX_SIZE is specified
#else
constexpr auto maxSize = MAX_SIZE;
#endif
To set the max size when compiling with gcc, you can compile the code with the option -DMAX_SIZE=<desired_size>
.
The issue I have with this is it involves using preprocessor macros to get the MAX_SIZE
argument from the compiler. Preprocessor macros are considered evil for many reasons (that I will not get into here because that isn't the point of the question).
Is there any way to achieve this functionality without using preprocessor macros? (I have up to C++20 available so feel free to go wild with your solutions -- well mostly, some of it isn't implemented yet by gcc 10)
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.