5

We just started learning template meta programming in C++11. As an exercise we wrote a program that outputs the binary representation of an int value. We came up with two possible implementation. The first one uses recursion with enum values whereas the second method uses a constexpr function.

Our expectation was that both implementation results in executables of the same size. However, the first implementation leads to 9064 bytes whereas the second has 9096 bytes. We don't mind the tiny difference in bytes but do not understand what causes the difference.

We compiled the program with GCC 4.8.2 without optimization flag, however, the same results are found the -O2 flag.

#include <iostream>
using namespace std;

template <int val>
struct Bin
{
    enum { value = 10 * Bin<(val >> 1)>::value + (val & 1) };
};

template <>
struct Bin<0>
{
    enum { value = 0 };
};

constexpr int bin(int val)
{
  return val == 0 ? 0 : (10 * bin(val >> 1) + (val & 1));
}


int main()
{
  // Option 1
  cout << Bin<5>::value  << '\n'
       << Bin<27>::value << '\n';

  // Option 2
  cout << bin(5) << '\n'
       << bin(27) << '\n';
}
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • This program looks simplistic enough to dissassemble and analyze. – Borsunho Aug 14 '15 at 10:51
  • 4
    `constexpr` functions are not guaranteed to be evaluated at compile time unless used in a context that requires a constant expression. – T.C. Aug 14 '15 at 10:51

1 Answers1

3

constexpr functions may be evaluated at compile-time. They are not required to.

For the code you provided, the compiler isn't indeed doing that and bin is getting called at runtime; this means the function cannot be thrown away from the assembly. By explicitly requiring the values to be constexpr with

constexpr auto i = bin(5), j = bin(27);

the calls to bin are done at compile-time as shown here. With

  cout << bin(5) << '\n'
      << bin(27) << '\n'; 

the relevant emitted code is

movl $5, %edi # Parameter
callq   bin(int) # Here's the call to bin
movl    std::cout, %edi
movl    %eax, %esi
callq   std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
[...]
movl    $27, %edi # parameter
callq   bin(int) # call to bin
movq    %rbx, %rdi
movl    %eax, %esi
callq   std::basic_ostream<char, std::char_traits<char> >::operator<<(int)

When the call is elided, the size is the same for both versions.

edmz
  • 8,220
  • 2
  • 26
  • 45
  • Thanks for your response. Is there a way to explicitly requiring the values to be `constexpr` without storing them into a variable? – Michiel uit het Broek Aug 14 '15 at 12:08
  • @MichielUitHetBroek The available ways to force `constexpr` functions to be evaluated at compile-time require additional coding (often optimized out) and the easiest is using a compile-time value. See [this](https://stackoverflow.com/questions/14248235/when-does-a-constexpr-function-get-evaluated-at-compile-time?rq=1) and related. – edmz Aug 14 '15 at 13:58
  • @edmz How do you see that the calls are done at compile time? What I see is `callq`s instructions, which are basically calling functions. How do you know this occurs at compile time by looking at the generated assembly code? Thanks – KeyC0de Sep 30 '18 at 07:28
  • @Nik-Lz: Have a look at the link I provided (3 yo and still working, cool): you'll see the compiler is issuing `movl $101, %esi` instead of a `callq` to `bin(5)`. – edmz Sep 30 '18 at 08:47
  • @edmz Yeah. All `callq`s are calls to `std::cout` which of course have to be done at runtime. Everything else is handled through movs and comparisons. No function calls. So yes, this indicates compile time computations. – KeyC0de Sep 30 '18 at 12:10