0

Here's a riddle.

Imagine I have the following C++ function:

template<uint8_t MASK>
uint8_t Foo(uint8_t val)
{
    uint8_t step = 0;
    uint8_t result = 0;
    if(MASK & 0x01) {result |= (val & 0x01) >> step; ++step;}
    if(MASK & 0x02) {result |= (val & 0x02) >> step; ++step;}
    //...etc...
    if(MASK & 0x80) {result |= (val & 0x80) >> step; ++step;}

    return result;
}

When I instantiate this function as such (all values below are just examples values):

uint8_t someval = Foo<0xAA>(44);

The compiler optimizes out the if statements in Foo() because it knows at compile time what the result of said if() statement are.

This is nice and well, but trying to do the same in C is problematic because of the creation of the local variable step.

If step wasn't there, you could do a large #define like this:

#define Foo(MASK, val) (\
    ((MASK & 0x01) ? (val & 0x01) : 0) | \
    ((MASK & 0x02) ? (val & 0x02) : 0) | \
    ...etc...
    ((MASK & 0x80) ? (val & 0x80) : 0) | \
    )

But with step, I'm kind of at an impasse. What can I do to obtain the same functionality of C++ template in C for C++ template function with local variables?

Note that using inline C functions is not an answer, as the compiler will not know at compile time the value of MASK, and thus all the comparisons will not be optimized and will accordingly be part of the final compiled output.

Also note that changing the #define to include the result value is also not an answer, since this changes the "function's" signature.

And lastly, I am fully aware that there may be no answer to this riddle.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
TRISAbits
  • 455
  • 4
  • 9
  • One does not instantiate functions. – Wug Aug 17 '12 at 19:48
  • Why macro instead of function ? Also i think in C you can use { } code block - there you can use variables just like in a function. – Zaffy Aug 17 '12 at 19:50
  • If the compiler knows the value of MASK at compile time in C++ (it has to if it's a template parameter), why doesn't it in C? – Dirk Holsopple Aug 17 '12 at 19:50
  • Also, couldn't you just pass it as a parameter? a la `Foo(0xAA, 44)` – Wug Aug 17 '12 at 19:51
  • 1
    Of course the compiler knows the value of `Foo(0xAA, 44)`. – Bo Persson Aug 17 '12 at 19:51
  • Why is the macro signature set in stone? It's anyhow different from the one using function templates. – eq- Aug 17 '12 at 19:55
  • There is an x86 assembly instruction to count the bits in a quantity. Your algorithm looks like it shifts the bits to the low end of the byte. You could probably use something like this: `uint8_t byte; int i = bitcount(byte); byte = ~(((int) 0x80) >> (8 - i)); return byte;` – Wug Aug 17 '12 at 19:56
  • no wait, it doesn't do that. im stumped – Wug Aug 17 '12 at 20:03
  • This is a great suggestion, but the algorithm in my question is complete bogus and only serves to illustrate the problem I am encountering. IT also needs to work on any architecture, not only x86. In fact, this code is running on a PIC18. – TRISAbits Aug 17 '12 at 20:08
  • Your assumption that compilers wouldn't be able to inline such code is also bogus. Macros are certainly a useful tool, but nowadays compilers are quite good in constant elimination of inlined code, don't underestimate them (and make your life difficult). – Jens Gustedt Aug 17 '12 at 22:12
  • @Wug you are misinformed. See http://stackoverflow.com/tags/instantiation/info – Johannes Schaub - litb Aug 18 '12 at 15:04

3 Answers3

5

Have your macro try to do what the template does; create an inline function--

#define Foo(MASK, val) inline uint8_t Foo_##MASK(uint8_t val) \
{ \
   uint8_t step = 0; \
    uint8_t result = 0; \
    if(MASK & 0x01) {result |= (val & 0x01) >> step; ++step;} \
    if(MASK & 0x02) {result |= (val & 0x02) >> step; ++step;} \
    //...etc...
    if(MASK & 0x80) {result |= (val & 0x80) >> step; ++step;} \
\
    return result;\
}
antlersoft
  • 14,636
  • 4
  • 35
  • 55
  • Nice one! (Although for some reason OP wanted something which would change the macro signature.) – eq- Aug 17 '12 at 20:01
  • Of course! Hind sight is always 20/20 I suppose. Thanks for the great suggestion! – TRISAbits Aug 17 '12 at 20:10
  • Actually, I'm not sure that this would work. If you have the following: int main(){uint8_t x = Foo(0xAA, 44); return 0;} you will generate a compiler error because the function body will be defined in main, when in fact the routine needs to be defined outside. – TRISAbits Aug 17 '12 at 21:24
  • 1
    @TRISAbits: you could split into two macros, one `#define InstantiateFoo(MASK) inline uint8_t Foo_##MASK` etc, which defines the function and you call it at file scope and a second `#define Foo(MASK, val) Foo_##MASK(val)` to call the appropriately defined function. You have an extra line to remember to put in manually but at least there will be a linker error if you forget it. – tinman Aug 17 '12 at 22:02
  • Yes you could, but it's not the same as using the C++ template version. Plus, you have to make sure that InstantiateFoo(MASK) is only used once, multiple invocations with the same MASK value will result in a compiler error. This doesn't sound like much of a limitation, but since MASK is a user generated value and could be used in multiple modules across the software, the possibility exists. Some form of inclusion guards for InstantiateFoo(MASK) could ensure that multiple calls to InstantiateFoo(MASK) do not cause compiler errors, but I'm not sure how this would work. Is there a way? – TRISAbits Aug 17 '12 at 22:29
  • In C, this doesn't work. It needs your function to be `static`, otherwise you can end up with "undefined reference" errors in your program if the compiler decides to do an external call to the inline function instead of an inline function call (see http://stackoverflow.com/questions/2217628/multiple-definition-of-inline-functions-when-linking-static-libs/2218034#2218034 ). If you make it `static`, it will be valid, but it will cause code bloat. – Johannes Schaub - litb Aug 18 '12 at 15:21
2

How about (GCCism):

#define Foo(MASK, val) ({ \
    uint8_t step = 0; \
    uint8_t result = 0; \
    if(MASK & 0x01) {result |= (val & 0x01) >> step; ++step;} \
    if(MASK & 0x02) {result |= (val & 0x02) >> step; ++step;} \
    //...etc...
    if(MASK & 0x80) {result |= (val & 0x80) >> step; ++step;} \
    result;})
eq-
  • 9,986
  • 36
  • 38
  • 1
    Note that this is a non-standard GCC extension called a [*statement expression*](http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html). – Adam Rosenfield Aug 17 '12 at 20:04
  • This is a good idea, but not all compilers support this feature. I'm also not sure if this mechanism is part of ANSI C (I'm sure I'll be corrected on the matter shortly). – TRISAbits Aug 17 '12 at 20:05
  • @TRISAbits, no, it's not and no, not all will. That's what *GCCism* means ;) – eq- Aug 17 '12 at 20:06
1

You are just making a false assumption

Note that using inline C functions is not an answer, as the compiler will not know at compile time the value of MASK, and thus all the comparisons will not be optimized and will accordingly be part of the final compiled output.

I just tested, gcc is able to inline everything without problem:

static inline
unsigned Foo(unsigned MASK, unsigned char val)
{
    unsigned char step = 0;
    unsigned char result = 0;
    if(MASK & 0x01) {result |= (val & 0x01) >> step; ++step;}
    if(MASK & 0x02) {result |= (val & 0x02) >> step; ++step;}
    //...etc...
    if(MASK & 0x80) {result |= (val & 0x80) >> step; ++step;}

    return result;
}

int main(int argc, char *argv[]) {
  return Foo(0x02, argc);

}

results in the following assembler for main:

main:
.LFB1:
    .cfi_startproc
    andl    $2, %edi
    movzbl  %dil, %eax
    ret
    .cfi_endproc
.LFE1:
    .size   main, .-main

and clang does functionally the same.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • Interesting. I do assume too much. Thanks for the inventigation! Too bad I can't use GCC on the target I am programming, since my compiler does not produce the same excellent out, even after I increase the inline code size threshold (the threshold where the compiler changes the inline function into a normal routine). – TRISAbits Aug 17 '12 at 22:45