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';
}
2
  • This program looks simplistic enough to dissassemble and analyze. Commented Aug 14, 2015 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. Commented Aug 14, 2015 at 10:51

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your response. Is there a way to explicitly requiring the values to be constexpr without storing them into a variable?
@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 and related.
@edmz How do you see that the calls are done at compile time? What I see is callqs instructions, which are basically calling functions. How do you know this occurs at compile time by looking at the generated assembly code? Thanks
@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 Yeah. All callqs 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.