1

Is it possible to have stringification after numeric evaluation?

This is better explained with a simple example:

#define A 1
#define B 2
#define SUM (A + B)

#define STR_IMPL_(x) #x
#define STR(x) STR_IMPL_(x)

char *sum = STR(SUM);

As written this generates the single line:

char *sum = "(1 + 2)";

Is it possible, somehow, to generate char *sum = "3"; instead?

I suspect this is not possible because CPP is a purely text processor (altough it can do arithmetic, at least in conditionals), but I might have overlooked something.

ZioByte
  • 2,690
  • 1
  • 32
  • 68
  • I recently looked for this, and all I found was a method using C++ templates. – ikegami Dec 06 '19 at 09:01
  • To a limit it's possible. [Boost PP](https://www.boost.org/doc/libs/1_71_0/libs/preprocessor/doc/index.html) supports something like that. Since it's a preprocessor header only library, you could probably use it. – StoryTeller - Unslander Monica Dec 06 '19 at 09:02
  • 1
    Possible duplicate of [Can the C preprocessor perform integer arithmetic?](https://stackoverflow.com/questions/1560357/can-the-c-preprocessor-perform-integer-arithmetic) – GSerg Dec 06 '19 at 09:04
  • @GSerg: The linked answer does not really answer question (deals with pure CPP math,, not its stringification). There are some apparently interesting pointers in answers, but they seem dead :( – ZioByte Dec 06 '19 at 10:08
  • The answer to the question is actually: no, you cannot do that in a way that is meaningful to your case, because you can't have generic pre-processor code. You would have to write some flavour of `#if (x) == 3 #define FOO "3" #endif`, or use non-standard extensions. – Lundin Dec 06 '19 at 11:56
  • @Lundin "You would have to write some flavor of" ...to evaluate expressions, yes. But you can write that. And for example to translation phase 4, `40500` is *just a token*; viz, a pp-number. That token can be produced by pasting `4`, `0`, `5`, `0`, and `0` (all pp-numbers in themselves) together in any order (as well as arbitrary numbers of placemarker-tokens left of that). So to support building the token `40500`, you don't need 40,500 `#if` directives; you just need one `#if/#elif/#else/#endif` chain per digit and the `##` operator. – H Walters Dec 06 '19 at 13:07
  • @Lundin [like this(wandbox)](https://wandbox.org/permlink/h7U4qSSWOecWSUya) (no boost; no extensions) – H Walters Dec 06 '19 at 13:44
  • @HWalters Yes you can solve almost any problem in C by writing a forest of repeating macros. But it isn't really feasible, in my opinion. One should always question why the need for such macros is there to begin with - this is a very typical "X Y problem". – Lundin Dec 06 '19 at 13:50
  • @HWalters Though I believe that the code in the link makes a far better answer than one using boost. – Lundin Dec 06 '19 at 13:51
  • @Lundin There is no disadvantage I see to using boost preprocesor other than to convince you specifically that no compiler extensions are required to implement this approach. The only *other* difference I see is that mine is an ancient proof of concept demo I happened to write, and a large number of professionals wrote the boost pp (mind you, a large number of professionals wrote the standard library you use too). – H Walters Dec 06 '19 at 14:05
  • @HWalters Except it's a fairly common situation in embedded systems that you can't use any libraries at all, especially not heavy ones with tonnes of heap allocation in them. Boost is completely out of the question most of the time. – Lundin Dec 06 '19 at 14:12
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/203759/discussion-between-h-walters-and-lundin). – H Walters Dec 06 '19 at 14:22

2 Answers2

2
Is it possible to have stringification after numeric evaluation?

Yes. For reference here are some specific definitions I'll use:

  • arithmetic The ability to perform calculations on numbers using common primitives such as adding, subtracting, multiplication, division, etc.
  • expression A grammatical representation of arithmetic using common operators such as parenthetical groupings, infix operators +, -, *, /, etc.

Arithmetic approach

Given these definitions, macro expansions cannot evaluate expressions, but they can perform arithmetic. Instead of the operators you would use macros, each of which would implement arithmetic from scratch. Here's a boost pp usage using this approach:

#include <boost/preprocessor/arithmetic.hpp>
#include <boost/preprocessor/stringize.hpp>

#define A 1
#define B 2
#define SUM BOOST_PP_ADD(A, B)
BOOST_PP_STRINGIZE(SUM)

Demo

With this approach, you get to simply expand a macro to a result; that result can then be stringified. But you're implementing arithmetic itself in macros as opposed to operators, and that takes a lot of macros. So to pull this off, either your number ranges need to be severely limited, or you need to use numbers that are decomposable to macro evaluation (e.g., represent 20000 as (2,0,0,0,0) as opposed to 20000). Boost pp arithmetic uses the former approach (works with ranges from 0 to 256; yes, I realize that's 257 numbers).

Expression approach

Alternately, you can evaluate expressions. As noted, the preprocessor can evaluate expressions in conditional directives. Using that as a primitive, you can tease out the result; e.g., if EXPRESSION expands to your expression, you can #define D0, representing the unit digit of the result, using a construct like this:

#if   ((EXPRESSION)%10)==9
#define D0 9
#elif ((EXPRESSION)%10)==8
#define D0 8
...

You can then similarly #define D1 to be the specific digit for the ten's place, D2 for the hundreds, etc... then have a RESULT macro expand to ... D3##D2##D1##D0. Wrap this entire thing into something like evaluator.hpp and you can pump an arbitrary expression in by defining EXPRESSION as your expression, using #include "evaluator.hpp" to evaluate it, and finally use RESULT to represent the result. With this approach each "evaluator" needs the specific Dx macros defined to the specific digits to work... so that behaves analogously to "variables", but consumes the entire evaluator.

Boost pp has this capability with each evaluator known as a "slot", and provides 5 slots. So you have to pump with #include's, and each evaluator can only store one result at a time... but in return your range is not restricted (more than native ranges) and you're actually evaluating expressions. Here's an example of using this approach using boost pp:

#include <boost/preprocessor/slot/slot.hpp>
#include <boost/preprocessor/stringize.hpp>

#define A 1
#define B 2
#define SUM (A+B)

#define BOOST_PP_VALUE SUM
#include BOOST_PP_ASSIGN_SLOT(1)

BOOST_PP_STRINGIZE(BOOST_PP_SLOT(1))

Demo

(Edit: Hand rolled evaluator here (wandbox) might be worth review to see how the mechanics explained above work).

TL;DR summary

    • arithmetic
    • pro: evaluates entirely by macro invocation
    • con: macros instead of infix operators
    • con: either ranges are limited or uses alternate literal representations
    • expressions
    • pro: evaluates actual expressions (infix, groups, etc)
    • pro: ranges as open as native ranges, with ordinary literals
    • con: requires `#include` to pump generic mechanism
    • con: reuse of evaluator must lose previous result
H Walters
  • 2,634
  • 1
  • 11
  • 13
  • This is tagged C. Boost is non-standard C++. – Lundin Dec 06 '19 at 11:48
  • 2
    @Lundin "The library supports **both C++ and C compilation**. It **does not depend on any other Boost libraries** and therefore may be used as a standalone library." [source](https://www.boost.org/doc/libs/1_71_0/libs/preprocessor/doc/index.html) – H Walters Dec 06 '19 at 12:33
1
#include <boost/preprocessor/arithmetic/add.hpp>

char* sum = STR(BOOST_PP_ADD(A,B));
Manthan Tilva
  • 3,135
  • 2
  • 17
  • 41
  • Is that supposed to work with "Plain C" or does it need C++? – ZioByte Dec 06 '19 at 10:10
  • ZioByte: As @StoryTeller mentioned, boost pp is a preprocessor only library; C++ and C share the same cpp, so yes it works in C ([demo](http://coliru.stacked-crooked.com/a/16119cb0bf430049)); note at bottom that I'm driving with gcc here not g++, not that it matters here anyway since this stops at the preprocessor. – H Walters Dec 06 '19 at 10:20