Skip to main content


WHAT—

https://andreasjhkarlsson.github.io//jekyll/update/2023/12/27/4-billion-if-statements.html

in reply to Charlie Stross

To demonstrate the power of C++, I have reimplemented the algorithm in a single file using the built-in metaprogramming capabilities of C++. No more manual source generation; no more Python!

#[url=https://infosec.exchange/tags/include]include[/url] <stdio.h><br>#[url=https://infosec.exchange/tags/include]include[/url] <stdint.h><br>#[url=https://infosec.exchange/tags/include]include[/url] <stdlib.h><br>#[url=https://infosec.exchange/tags/include]include[/url] <limits><br><br>using T = uint8_t;<br><br>namespace {<br>    template<T><br>    void do_check(T, const char *, const char *);<br><br>    template<><br>    void do_check<0>(T n, const char *msg_yes, const char *) {<br>        if (n == 0) {<br>            puts(msg_yes);<br>        }<br>    }<br><br>    template<T limit><br>    void do_check(T n, const char *msg_yes, const char *msg_no) {<br>        do_check<limit - 1>(n, msg_no, msg_yes);<br>        if (n == limit) {<br>            puts(msg_yes);<br>        }<br>    }<br>}<br><br>static void check(T n) {<br>    do_check<std::numeric_limits<T>::max()>(n, "odd", "even");<br>}<br><br>int main(int, char **argv) {<br>    check(atoi(argv[1]));<br>}<br>

Compiler explorer: https://godbolt.org/z/434bPMP3M

To generate the comparisons, we use template metaprogramming. However, there are no template loops. C++ templates can be classified as an esoteric purely functional programming language, so we have to use pattern matching and recursion.

The do_check<N> template generates comparisons up to N. Our base case is do_check<0>, which compares to 0 and does nothing else. The general (inductive) case calls do_check<N - 1> and then compares to N.

It is interesting to look at the generated code. G++ translates the algorithm faithfully, if slightly chaotically (look at the order of labels and comparisons in the source; it jumps all over the place). Clang, on the other hand, implements one of the optimizations suggested by @gkemp. It fills a big array with messages (messages[256] = { "even", "odd", "even", "odd", ... }) and then outputs messages[n] (there is only a single call to puts in the generated assembler code).

This covers numbers from 0 to 255 (the range of uint8_t). We can extend the range by switching to using T = uint16_t, although doing so naively runs into the following error:

x.cc:21:28: fatal error: template instantiation depth exceeds maximum of 900 (use ‘-ftemplate-depth=’ to increase the maximum)<br>

Fortunately it tells us how to fix it and with g++ -ftemplate-depth=65536 we are right back in business. Well, after waiting 2½ minutes for the compilation to finish, that is. (Clang is less fortunate: It crashes with a segmentation fault after exhausting the available stack space, at least on Compiler Explorer.)

I haven't dared try T = uint32_t yet.

#CPlusPlus #templates