4

I need a Macro that would be evaluated at compile time, something like:

#define FIND_RANGE(x) \
if x>16 \
32 \
elif x>8 \
16 \
elif x>4 \
8 \
elif x>2 \
4 \
elif x>1 \
2 \
else \
1 \
endif \

So the code

#define S1 FIND_RANGE(7)
unsinged int i = S1;

would be sent to compiler as

unsinged int i = 8;

Can this simple algorithm be done so it is evaluated at compile time?

Danijel
  • 8,198
  • 18
  • 69
  • 133
  • 1
    AFAIK you cannot do this in standard C or C++, but gcc's preprocessor has [this](https://gcc.gnu.org/onlinedocs/cpp/Directives-Within-Macro-Arguments.html#Directives-Within-Macro-Arguments), maybe this can help: – Jabberwocky Jan 19 '17 at 10:03
  • 2
    `(x > 16 ? 32 : x > 8 ? 16 : x > 4 ? 8` etc. – M.M Jan 19 '17 at 10:11
  • 1
    It looks like you want to [round up to the next power of 2](http://stackoverflow.com/questions/466204/rounding-up-to-nearest-power-of-2), or [specifically eg.](http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2) – Sander De Dycker Jan 19 '17 at 10:13
  • @M.M That works, many thanks. – Danijel Jan 24 '17 at 07:04

4 Answers4

9

While C has no constexpr functions, both GCC and Clang can evaluate simple functions at compile-time with -O1. The related optimization is known as constant folding.

The following C code:

#include <stdio.h>

static inline unsigned int findRange(unsigned int x)
{
    if (x > 16)
        return 32;
    else if (x > 8)
        return 16;
    else if (x > 4)
        return 8;
    else if (x > 2)
        return 4;
    else if (x > 1)
        return 2;
    return 1;
}

int main(void)
{
    unsigned int i = findRange(7);
    printf("%u\n", i);
    return 0;
}

results into x86-64 assembly code (reference: godbolt.org/g/kVYe0u):

main:
        sub     rsp, 8
        mov     esi, 8
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        add     rsp, 8
        ret

As you can see, the call to findRange is subsituted by value, which is computed at compile-time.

This works even when findRange is defined as normal (non-inline) function with external linkage.

Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
  • Thanks for the effort. There is a simpler solution, see M.M suggestion. – Danijel Jan 24 '17 at 07:19
  • 2
    @Danijel: The M.M suggestion is neat, as it allows to pack conditions into single expression, rather than multiple statements. The point of my answer is to proof, that it is no longer necessary, since the modern compiler will optimize the code (yielding the final result), while it's more readable and safe (e.g. consider `SCALE(x++)`). – Grzegorz Szpetkowski Jan 24 '17 at 11:47
3

I don't think you can do that that easy. The problem is that the conditionals available to the preprocessor comes as preprocessor directives.

What you can do however is to use the #include directive creatively to create more advanced constructs. Create find-range.mac as:

#if x>16
32
#elif x>8
16
#elif x>4
8
#elif x>2
4
#elif x>1
2
#else
1
#endif
#undef x

and then use it as:

int i = 
#define x 7
#include "find-range.mac"
;

Which should expand to something like:

int i =
8
;

Another trick that does not go all the way is to do replace FIND_RANGE(x) with FIND_RANGEx by gluing and then define FIND_RANGEx appropriately. This requires x to be in a finite set of values:

#define FIND_RANGE(x) FIND_RANGE ## x
#define FIND_RANGE1 1
#define FIND_RANGE2 2
#define FIND_RANGE3 4
#define FIND_RANGE4 4
#define FIND_RANGE5 8
#define FIND_RANGE6 8
#define FIND_RANGE7 8
#define FIND_RANGE8 8
// etc...
skyking
  • 13,817
  • 1
  • 35
  • 57
2

For a bit of recreation, I translated that bit twiddling hack mentioned by Sander into a macro:

#define XS(x,y) (x | (x>>y))
#define FR(x) XS(XS(XS(XS(XS(x-1,1),2),4),8),16)+1

So FR(7) should give 8 at compile time, and so on.

(*But for all practical purposes an answer by Grzegorz Szpetkowski is the one to refer to.)

dbrank0
  • 9,026
  • 2
  • 37
  • 55
  • Sorry, had no time to test this. Since speed is not the issue here, I used a more straightforward solution: `(x > 16 ? 32 : x > 8 ? 16 : x > 4 ? 8`, etc. – Danijel Jan 24 '17 at 07:06
  • Sure, I would never use above macro for anything but a proof of concept either. – dbrank0 Jan 24 '17 at 10:36
1

Turns out it is doable, and even simple:

#define POW00          1.0f
#define POW01          2.0f
#define POW02          4.0f
#define POW03          8.0f
#define POW04         16.0f
#define POW05         32.0f
#define POW06         64.0f
#define POW07        128.0f
#define POW08        256.0f  // use some nicer pow2 constant generation

#define SCALE(x) ( \
x > POW07 ? POW08 : \
x > POW06 ? POW07 : \
x > POW05 ? POW06 : \
x > POW04 ? POW05 : \
x > POW03 ? POW04 : \
x > POW02 ? POW03 : \
x > POW01 ? POW02 : \
x > POW00 ? POW01 : POW00 \
) // end SCALE

Example:

int main()
{
   float a = (float)SCALE(7.0f);
}

This gets evaluated at compile time to

float a = 8.0f;
Danijel
  • 8,198
  • 18
  • 69
  • 133