Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Metaprogramming with constexpr or struct

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';
}
like image 914
Michiel uit het Broek Avatar asked Oct 19 '22 04:10

Michiel uit het Broek


1 Answers

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.

like image 149
edmz Avatar answered Oct 21 '22 21:10

edmz