Skip to content

Instantly share code, notes, and snippets.

@phadej
Last active December 17, 2015 22:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phadej/5683510 to your computer and use it in GitHub Desktop.
Save phadej/5683510 to your computer and use it in GitHub Desktop.

Forcing compiler to evaluate constexpr expressions

With a code like in constexpr.cc the outputted llvm assember for the main function looks like

Compiled with clang++ -S -emit-llvm -std=c++11 constexpr.cc

define i32 @main() uwtable ssp {
  %1 = call %"class.std::basic_ostream"* @_ZNSolsEi(%"class.std::basic_ostream"* @_ZSt4cout, i32 7)
  %2 = call %"class.std::basic_ostream"* @_ZNSolsEPFRSoS_E(%"class.std::basic_ostream"* %1, %"class.std::basic_ostream"* (%"class.std::basic_ostream"*)* @_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_)
  %3 = call i32 @_Z3fibj(i32 10)
  %4 = call %"class.std::basic_ostream"* @_ZNSolsEj(%"class.std::basic_ostream"* @_ZSt4cout, i32 %3)
  %5 = call %"class.std::basic_ostream"* @_ZNSolsEPFRSoS_E(%"class.std::basic_ostream"* %4, %"class.std::basic_ostream"* (%"class.std::basic_ostream"*)* @_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_)
  ret i32 0
}

if we add CT macro around fib call (constexpr2.cc) the result will be

define i32 @main() uwtable ssp {
  %1 = call %"class.std::basic_ostream"* @_ZNSolsEi(%"class.std::basic_ostream"* @_ZSt4cout, i32 7)
  %2 = call %"class.std::basic_ostream"* @_ZNSolsEPFRSoS_E(%"class.std::basic_ostream"* %1, %"class.std::basic_ostream"* (%"class.std::basic_ostream"*)* @_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_)
  %3 = call %"class.std::basic_ostream"* @_ZNSolsEj(%"class.std::basic_ostream"* @_ZSt4cout, i32 89)
  %4 = call %"class.std::basic_ostream"* @_ZNSolsEPFRSoS_E(%"class.std::basic_ostream"* %3, %"class.std::basic_ostream"* (%"class.std::basic_ostream"*)* @_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_)
  ret i32 0
}

Compiler already calculated the fib(10) value for us!

Even if we add optimizer flag -O2 in the former case I can see

  %13 = tail call i32 @_Z3fibj(i32 10)
  %14 = zext i32 %13 to i64
  %15 = tail call %"class.std::basic_ostream"* @_ZNSo9_M_insertImEERSoT_(%"class.std::basic_ostream"* @_ZSt4cout, i64 %14)

in the main body. In the latter case we can see pre calculated value already

  %13 = tail call %"class.std::basic_ostream"* @_ZNSo9_M_insertImEERSoT_(%"class.std::basic_ostream"* @_ZSt4cout, i64 89)

Unfortunately this trick works only for integral expressions. We cannot do eg. mathematical vector calculations vec.cc iF we try to uncommend second of the main function line we get an error:

vec.cc:12:25: error: a non-type template parameter cannot have type 'vec'
template <typename T, T V>
                        ^
vec.cc:22:15: note: while substituting prior template arguments into non-type template parameter 'V' [with T = vec]
        std::cout << CT(vec(1,2) + vec(3,4)).x << std::endl; // doesn't compile
                     ^~~~~~~~~~~~~~~~~~~~~~~
vec.cc:17:15: note: expanded from macro 'CT'
#define CT(x) compile_time<decltype(x), x>::value
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

There is already a proposal to standard: Allowing arbitrary literal types for non-type template parameters This can be seen as bringing even more and richer dependent type stuff into c++.

Actually there is similar structure in c++11 already, std::integral_constant. And it's called integral constant for a reason.

Other approach

As you can find out, eg from Stack Overflow You can assign the constexpr to the constexpr variable, and use it, vec2.cc. Then the result will be:

  %5 = call %"class.std::basic_ostream"* @_ZNSolsEi(%"class.std::basic_ostream"* @_ZSt4cout, i32 66)

So you can force evaluation of constexpr at compile time, but still, you cannot have arbitrary literals as template parameters, for now.

Experimented with

Apple clang version 4.0 (tags/Apple/clang-421.0.57) (based on LLVM 3.1svn)
Target: x86_64-apple-darwin12.3.0
Thread model: posix
template <typename T, T V>
struct compile_time {
static const T value = V;
};
#define CT(x) compile_time<decltype(x), x>::value
constexpr unsigned int fib(unsigned int n) {
return n == 0 || n == 1 ? 1 : fib(n-2) + fib(n-1);
}
#include <iostream>
int main() {
std::cout << CT(1+2*3) << std::endl;
std::cout << fib(10) << std::endl;
}
template <typename T, T V>
struct compile_time {
static const T value = V;
};
#define CT(x) compile_time<decltype(x), x>::value
constexpr unsigned int fib(unsigned int n) {
return n == 0 || n == 1 ? 1 : fib(n-2) + fib(n-1);
}
#include <iostream>
int main() {
std::cout << CT(1+2*3) << std::endl;
std::cout << CT(fib(10)) << std::endl;
}
struct vec {
int x;
int y;
constexpr vec(int x, int y) : x(x), y(y) {}
};
constexpr vec operator+(const vec &a, const vec &b) {
return vec(a.x + b.x, a.y + b.y);
}
template <typename T, T V>
struct compile_time {
static const T value = V;
};
#define CT(x) compile_time<decltype(x), x>::value
#include <iostream>
int main() {
std::cout << (vec(1,2) + vec(3,4)).x << std::endl;
// std::cout << CT(vec(1,2) + vec(3,4)).x << std::endl; // doesn't compile
}
struct vec {
int x;
int y;
constexpr vec(int x, int y) : x(x), y(y) {}
};
constexpr vec operator+(const vec &a, const vec &b) {
return vec(a.x + b.x, a.y + b.y);
}
template <typename T, T V>
struct compile_time {
static const T value = V;
};
#define CT(x) compile_time<decltype(x), x>::value
#include <iostream>
int main() {
constexpr vec sum = vec(22,33) + vec(44,55);
std::cout << sum.x << std::endl;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment