views:

53

answers:

3

I am using BOOST_PP for to do precompile computations in the preprocessor. I am focusing on an application where code size is extremely important to me. (So please don't say the compiler should or usually does that, I need to control what is performed at compile time and what code is generated). However, I want to be able to use the same name of the macro/function for both integer constants and variables. As trivial example, I can have

#define TWICE(n) BOOST_PP_MUL(n,2)
//.....
// somewhere else in code
int a = TWICE(5);

This does what I want it to, evaluating to

int a = 10;

during compile time.

However, I also want it to be used at

int b = 5;
int a = TWICE(b);

This should be preprocessed to

int b = 5;
int a = 5 * 2;

Of course, I can do so by using the traditional macros like

#define TWICE(n) n * 2

But then it doesnt do what I want it to do for integer constants (evaluating them during compile time).

So, my question is, is there a trick to check whether the argument is a literal or a variable, and then use different definitions. i.e., something like this:

#define TWICE(n) BOOST_PP_IF( _IS_CONSTANT(n), \
                              BOOST_PP_MUL(n,2), \
                              n * 2 )

edit: So what I am really after is some way to check if something is a constant available at compile time, and hence a good argument for the BOOST_PP_ functions. I realize that this is different from what most people expect from a preprocessor and general programming recommendations. But there is no wrong way of programming, so please don't hate on the question if you disagree with its philosophy. There is a reason the BOOST_PP library exists, and this question is in the same spirit. It might just not be possible though.

+1  A: 

not quite direct approach, however:

struct operation {
    template<int N>
    struct compile {
        static const int value = N;
    };
    static int runtime(int N) { return N; }
};

operation::compile<5>::value;
operation::runtime(5);

alternatively

operation<5>();
operation(5);
aaa
hmmm, it still seems like two functions, called differently. They just seem to reside in the same struct. I could just as well make two different macros, like in my example and just call the appropriate one, dependent on whether I want compile time or runtime computations. I was actually aiming for one interface that selects the right code.
highBandWidth
@highBandWidth ah ok, i see ... might be difficult to do
aaa
+1  A: 

There is no real chance of mixing the two levels (preprocessor and evaluation of variables). From what I understand from you question, b should be a symbolic constant?

I think you should use the traditional one

#define TWICE(n) ((n) * 2)

but then instead of initializing variables with expressions, you should initialize them with compile time constants. The only thing that I see to force evaluation at compile time and to have symbolic constants in C are integral enumeration constants. These are defined to have type int and are evaluated at compile time.

enum { bInit = 5 };
int b = bInit;
enum { aInit = TWICE(bInit) };
int a = aInit; 

And generally you should not be too thrifty with const (as for your b) and check the produced assembler with -S.

Jens Gustedt
I see what you mean. I am actually trying to write a library where code size is extremely important, and I was trying to give one interface that can be called with both constants and with variables. In this case, they could be true variables, which have to be evaluated at run time. May be it cannot be done.
highBandWidth
@highBandWidth: If this is so important for you, you should really identify the points where these are compile time expressions and do for these as in my answer. If you do so, the compiler will immediately tell you if you try to declare an enum constant with a dynamic expression, so this is good test. For other places you can't runtime evaluation then, so you'd have to go with the classical macros, anyhow.
Jens Gustedt
@Jens Gustedt: I agree. I was trying to write a library though, so I don't have the option of checking the compile time issues and then going back to change the code.
highBandWidth
+2  A: 

You're trying to do something that is better left to the optimizations of the compiler.

int main (void) {
  int b = 5;
  int a = b * 2;

  return a; // return it so we use a and it's not optimized away
}

gcc -O3 -s t.c

 .file "t.c"
 .text
 .p2align 4,,15
.globl main
 .type main, @function
main:
.LFB0:
 .cfi_startproc
 movl $10, %eax
 ret
 .cfi_endproc
.LFE0:
 .size main, .-main
 .ident "GCC: (Debian 4.5.0-6) 4.5.1 20100617 (prerelease)"
 .section .note.GNU-stack,"",@progbits

Optimizing compiler will optimize.

EDIT: I'm aware that you don't want to hear that the compiler "should" or "usually" does that. However, what you are trying to do is not something that is meant to be done in the C preprocessor; the people who designed the C language and the C preprocessor designed it to work with the preprocessing token as it's basic atom. The CPP is, in many ways, "dumb". This is not a bad thing (in fact, this is in many cases what makes it so useful), but at the end of the day, it is a preprocessor. It preprocesses source files before they are parsed. It preprocesses source files before semantic analysis occurs. It preprocesses source files before the validity of a given source file is checked. I understand that you do not want to hear that this is something that the parser and semantic analyzer should handle or usually does. However, this is the reality of the situation. If you want to design code that is incredibly small, then you should rely on your compiler to do it's job instead of trying to create preprocessor constructs to do the work. Think of it this way: thousands of hours of work went into your compiler, so try to reuse as much of that work as possible!

wash
Thanks! I like your answer. Seeing the assembly code I can see that the compiler does optimize it. I am not sure if it will do so for higher order functions, and where they involve control logic. But the only way to see is by experimenting so I will go back and check it out for the functions I am interested in.
highBandWidth
The best thing to do here is to experiment. If you're using GCC or clang, there's a whole host of ways in which you can inspect what the compiler is doing and what the compiler is not doing. You might even want to check out the clang static analyzer, a very cool tool for examine how C or C++ source code is compiled. Visual Studio also has superb tools for this sort of stuff.
wash
http://clang-analyzer.llvm.org/ <- link to the clang static analyzer.
wash