1

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)

tjwrona1992
  • 8,614
  • 8
  • 35
  • 98
  • 14
    *Preprocessor macros are considered evil* They are only evil when used not for the job they were intended for, like `goto`. This is a perfect use case for them. – NathanOliver Feb 13 '20 at 22:28
  • @NathanOliver I've always heard that they are evil as a blanket statement where no exceptions are mentioned... but I really can't think of any other possible way this can be done so you are probably right lol – tjwrona1992 Feb 13 '20 at 22:29
  • 3
    You can just have a header file `config.h` or similar, in which you can set e.g. `maxSize` directly. – walnut Feb 13 '20 at 22:31
  • 2
    Generally exceptions aren't given because its "newbs" that are asking and they don't need to know about the "advance" exceptions. It's just easier to say never use it and then once they really have a case, have them ask if it would be okay then. – NathanOliver Feb 13 '20 at 22:31
  • @walnut Not enterprise-y enough. Needs a custom GCC plugin to read XML data from a cloud based NoSQL type database and inject it directly into the code stream, with a Node.js web front end to allow the user to change the value. (Did I miss any buzzwords?) – Peter M Feb 13 '20 at 22:36
  • Is the ternary operator an option in this particular situation? Something like: `constexpr auto maxSize = MAX_SIZE ? MAX_SIZE : 42;`. – André Caceres Feb 13 '20 at 22:37
  • 2
    @AndréCaceres `MAX_SIZE` may or may not be defined. – NathanOliver Feb 13 '20 at 22:39
  • 1
    @AndréCaceres The issue isn't defining maxSize, it's how to inject MAX_SIZE into the code – Peter M Feb 13 '20 at 22:40
  • Can you go one step back? I.e. instead of thinking about how to get the MAX_SIZE into compiling, can you find a different way which does not need it? For example, if you could avoid a static array by switching to something dynamic. – Yunnosch Feb 13 '20 at 22:51
  • @NathanOliver I think there is a mis-read here in the problem space. The OP is attempting to parameterize compile-time constants using a macro, read carefully, because: "The only way I know of is to make it a configurable option passed to the compiler." Why it's "proper" to use macros, and when macros is okay, I'm utterly convinced is a red herring. The OP is not *trying* to inject macros into his code; he's *trying* to perform parameterized compile-time operations. That in mind (leading rhetorical question), isn't there another way to parameterize array sizes/calculations than macros? – H Walters Feb 13 '20 at 22:54
  • @HWalters I know, I'm just pointing out to the OP that "MACROS == EVIL" is not 100% true. Without modifying a file the only way I know of way to inject a constant into the source code is to define a macro like this. – NathanOliver Feb 13 '20 at 22:56
  • Okay, just trying to make sure people aren't lead down the garden path (and trying really hard to pass on giving an answer, since there are tons of equally qualified s.o. ppl) – H Walters Feb 13 '20 at 22:57
  • @Yunnosch it has to be static because computing the values of the array is somewhat expensive, but all of the inputs are known at compile-time so moving the computation to compile time greatly improves the application performance. – tjwrona1992 Feb 13 '20 at 23:01
  • Write the number to a file, and have `constexpr auto maxsize = ` `#include "maxsize.txt"` `;` ? – M.M Feb 14 '20 at 04:32
  • @M.M I could do that, but then it involves a code change if you want to change the value (that is unless I have some extra "configuration" step before the build that generates that file.) – tjwrona1992 Feb 14 '20 at 15:23
  • Also consider that constexpr functions with constexpt arguments are evaluated at compile time too. You can do a lot with them in C++20. You might be able to write your array contents using constexpr functions. – TrentP Feb 15 '20 at 05:23
  • @TrentP exactly. That is what I am going for. :) However I need C++20 specifically for `consteval` because it lets me evaluate things that fail to compile with `constexpr`. Still not fully sure how it works but its almost like magic. – tjwrona1992 Feb 15 '20 at 05:42

2 Answers2

1

There is no other compiler based mechanism besides macro definition in GCC to parametrise compilation that I know of.

Outside of the compiler, you can use meta programming: Write a program that generates the source prior to compilation. This is essentially same as using the preprocessor, except you get to pick any language or tool of your choice instead of the standard preprocessor.

Downside of this choice is added complexity of building due to additional step. This approach can be used to side-step the problems of the preprocessor at the cost of potentially introducing new problems of the custom processor.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Ahhh I think this explains why tools like `gcc` have a "configure" step prior to compilation if you build them from source. I feel like adding a "configure" step is probably overkill for my application though since I only have one argument to pass. – tjwrona1992 Feb 13 '20 at 23:04
1

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.

TrentP
  • 4,240
  • 24
  • 35
  • I completely forgot that CMake could do things like this! Although the CMake preprocessor isn't that powerful I think that may be where I want to define this because then the syntax won't be dependent on the compiler. Also using CMake means I won't require an additional configuration step before CMake! – tjwrona1992 Feb 14 '20 at 21:30