Abstract:
Inlined and optimized functions can be used as macros.

Created by Peter Kankowski
Last changed
Filed under Interfaces

Share on social sitesReddit Digg Delicious Buzz Facebook Twitter

Metaprogramming with aggressive inlining

Macros are language constructs for generating boilerplate code. An inlined and optimized function with constant arguments can be a particular case of a LISP-like macro. Most C++ compilers support inlining and dead code elimination, so you can do some limited metaprogramming with them.

Checking modifier keys

Here is a small example. When handling WM_KEYDOWN message, you often need to check the state of modifier keys (Alt, Ctrl, and Shift) with GetKeyState. The function should usually be called 3 times for each modifier key. This would allow you to distinguish, say, Shift+F2 from Alt+Shift+F2. However, there are some cases where you would wish to ignore one modifier key.

The natural solution is a function taking two arguments: the desired key state (expressed with bit flags; one bit for each modifier key) and the mask of keys to check for. The function returns true if the needed keys were pressed and all other keys were not pressed:

// Implementation
BOOL inline KEY_PRESSED(int key) {
    return GetKeyState(key) < 0;
}

#define MOD_NO 0
BOOL __forceinline CheckModifiers(INT keys,
                                  INT mask = MOD_ALT | MOD_CTRL | MOD_SHIFT)
{
    return
        ( (mask & MOD_ALT  ) == 0 || ( keys       & 1) == KEY_PRESSED(VK_ALT  ) ) &&
        ( (mask & MOD_CTRL ) == 0 || ((keys >> 1) & 1) == KEY_PRESSED(VK_CTRL ) ) &&
        ( (mask & MOD_SHIFT) == 0 || ((keys >> 2) & 1) == KEY_PRESSED(VK_SHIFT) );
}

// Usage
case WM_KEYDOWN:
    if(wParam == 'C' && CheckModifiers(MOD_CTRL))
        CopyText();
    else if(wParam == VK_TAB && // Ctrl+Tab or Ctrl+Shift+Tab
            CheckModifiers(MOD_CTRL, MOD_CTRL | MOD_ALT))
        SwitchTabs();

The unneeded function calls are optimized away by MSVC, so this code will be effectively generated:

// if(wParam == 'C' && CheckModifiers(MOD_CTRL))
if(wParam == 'C' && GetKeyState(VK_ALT) >= 0 &&
    GetKeyState(VK_CTRL) < 0 && GetKeyState(VK_SHIFT) >= 0)

// if(wParam == VK_TAB && CheckModifiers(MOD_CTRL, MOD_CTRL | MOD_ALT))
if(wParam == VK_TAB && GetKeyState(VK_ALT) >= 0 &&
    GetKeyState(VK_CTRL) < 0)

The CheckModifiers function works as a LISP macro here.

Using small languages

In more complex cases, the compiler cannot optimize function calls. For example, sprintf with a constant format string could be replaced with appropriate conversion functions for each argument:

// sprintf(s, "%d items, %d seconds", items, seconds);
char * p = s;
p = print_int(p, items);
p = print_str(p, " items, ");
p = print_int(p, seconds);
    print_str(p, " seconds");

This would be more effective than string interpretation at run time. Most compilers do not perform such aggressive optimization, but it's theoretically possible. FreePascal implements writeln in such fashion, but it works only for that function. A generalization would make little languages (regular expressions, sscanf, printf, etc.) nearly as effective as hand-written code.

Applying functions without side effects to constants

PRR found another example: calling strlen function with a literal string, which can be optimized by MSVC and GCC. It would be more useful if you could use this trick for any function without side effects. For example, why can't you define the pi number through arcsine?

const double M_PI = 2 * asin(1); // Not valid C++

Or write an integer constant in binary using your own conversion function:

const int N = from_binary("010111"); // Not valid C++

In C++, you have to use tricky preprocessor macros or magic numbers for that.

An idea: unifying macros and functions

The asin and from_binary can be useful when called as a macro (with constant arguments) or a function (with variables). Instead of separating these concepts (as LISP does), a programming language could allow you to specify when an actual argument is substituted for the given formal parameter, at compile time or at run time. Something like that:

int sprintf(char * buffer, compile_time const char * format, ...); // Not valid C++

If format is a constant, it will be propagated to the function body and the resulting code will be inlined to the place where sprintf was called. If format is a variable, sprintf will be called as a usual function. What do you think about this idea?

Peter Kankowski
Peter Kankowski

About the author

Peter is the developer of Aba Search and Replace, a tool for replacing text in multiple files. He likes to program in C with a bit of C++, also in x86 assembly language, Python, and PHP.

Created by Peter Kankowski
Last changed

7 comments

Anonymous,

The D language allows you to evaluate ordinary functions at compile time (provided they don't use any libraries). BTW, seems like the comment form does not work on Google Chrome.

Peter Kankowski,

Thank you! This feature is exactly what Ace asked about. It cannot be applied to sprintf, though.

The comment form works okay on Google Chrome (just checked it). Probably, a temporary server problem; sorry for that.

ace,
An inlined and optimized function with constant arguments can be a particular case of a LISP-like macro.

Well it's just inlining and optimizing away dead code, no need calling it something else.

Most C++ compilers support constant propagation, so you can do some limited metaprogramming with them.

I wouldn't call your example metaprogramming. It's just inlining and optmizing away the dead code: once it sees:

if ( 0 ) something()

it removes the whole thing. The zero came from the compile time constant subexpression evaluation, not from "constant propagation."

The way most metaprogramming is done in C++ is by using templates. It was not planned by authors, and it gets quite ugly quite fast. One of the guys who produced some of the craziest code in C++ went to D afterwards:

http://en.wikipedia.org/wiki/Andrei_Alexandrescu

What's the purpose of the "compile_time"? Even now compiler would inline your function if you declare your function as inline. Did you want a keyword to avoid inlining when the parameter is not constant?

Using the knowledge that exists in compiler context at the compile time was a technique ignored for too long in C++. I'm very happy that the next version will allow me to write:

auto x = someFunctionReturningSomeType();

Not to mention that

auto i = vector<int>::begin();

is so much nicer than

vector<int>::iterator i = vector<int>::begin();

What I'd like to have in the language is the possibility to obtain the string names of the language elements in compile time and the type of the elements in the compile time, basically, to be able to process in compile time the things that are in that moment in the symbol tables of the compiler (together with the semantic to navigate through the symbol table, like "what's the return type of the function I'm in, what's the name of the function I'm in (as string) etc"). It would allow me to do some interesting constructs, especially because things can be optimized away, as you recognized. Somebody would say, generate the code with some external tool, but external tools don't have access to the symbol table the way the compiler has. I think D also has something of that (I haven't spent enough time to analyze though).

Peter Kankowski,
Did you want a keyword to avoid inlining when the parameter is not constant?

Yes, exactly. It would be useful to suggest aggressive inlining to the compiler sometimes.

ace,

I've found the specs about D

As far as I understand this is an example of evaluating string expression in compile time and making the resulting string to be compiled: http://www.digitalmars.com/d/2.0/mixin.html

Example of querying the compiler to the string value of some type, even the resulting type of an expression (stringof): http://digimars.com/d/2.0/property.html

I haven't tried, just read.

Peter Kankowski,

Thanks! D 2.0 is an interesting language. I checked code optimization in D in 2003, and it was very poor (here is my article in Russian). It's time to check it again :)

ace,

The first time the I've recognized these features of D was as I've found:

Generating Truly Optimal CodeUsing a Metaprogramming Library (Don Clugston, First D Programming Conference, 24 August 2007) http://s3.amazonaws.com/dconf2007/Don.ppt

Your article looks interesting, thanks. (Nitpicking: MinGW is not the name of the compiler but of the environment, the compiler evaluated is probably GCC. Also when comparing the code produced by compiler, the version which produced the demonstrated code is also important, as those things change.)

Your name:


Comment: