Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Branching on constexpr evaluation / overloading on constexpr

Tags:

The setup:

I have a function that uses SIMD intrinsics and would like to use it inside some constexpr functions.

For that, I need to make it constexpr. However, the SIMD intrinsics are not marked constexpr, and the constant evaluator of the compiler cannot handle them.

I tried replacing the SIMD intrinsics with a C++ constexpr implementation that does the same thing. The function became 3.5x slower at run-time, but I was able to use it at compile-time (yay?).

The problem:

How can I use this function inside constant expressions without slowing down my program at run-time?

Some ideas:

  • Adding support for constant evaluating all SIMD intrinsics to the compiler constant expression evaluator, for all compilers: probably the right solution, but an impossible titanic task.

More pragmatic solutions would be to either:

  • overload a function depending on whether it is being executed inside a constant expression (that is, provide a constexpr, and a non-constexpr version).
  • or, somehow branch inside a constexpr function between the constexpr and run-time implementation (that is, detect in a branch whether the function is being executed inside a constant expression).

Anyhow, I am open to any suggestion that solves my problem.

Hints:

  • @RMartinhoFernandes suggested in the Lounge to use __builtin_constant_p to detect whether the function arguments are all constant expressions, in which case the compiler would hopefully be at least attempting to evaluate the function at compile-time.

Failed attempts:

  • @Jarod42 made the straight forward suggestion of just using two independent functions. I would briefly like to point out why this cannot work because it is not trivial. This solution assumes that at the call-site it is known whether the function will be constexpr evaluated or not. But this is not the case. Consider a constexpr function calling mine, which version of my function should it pick? It must pick the constexpr one in order for it to compile, but that "outer" constexpr function could still be evaluated at run-time. In that case, it would use the "slow" compile-time implementation, and hence, this approach does not solve the problem.
like image 487
gnzlbg Avatar asked Feb 09 '17 09:02

gnzlbg


1 Answers

I would do it like this

constexpr int doit(int input, bool inconst = false) {
   return inconst ? doitconsty(input) : doitfast(input);
}

If the call to doit is inside of a constexpr function that can be called to perform something either at runtime or at compile time, just forward the flag

constexpr int f(int n, bool inconst = false) {
   /* ... */
   int importantInt = doit(n / 42, inconst);
   /* ... */
   return magicResult;
}

Any constexpr evaluation has something where it starts, if I'm not mistaken. Pass the inconst there

enum foo { bar = f(256, true) }

If you are in the runtime world, just call f like anything else

int main() { std::cout << "test-case: " << f(256); }

It should be noted that this does not work for operators, because you can't add the boolean parameter there. Instead, you could pass the value in some different way, if that's fine for you (for primitive values like int and bool, we could not overload the operator either).

template<typename T>
struct maybe_const_value {
   T t;
   bool isconst;
};

enum foo { bar = maybe_const_value{256, true} % magicTransform }; 

int main() { return maybe_const_value{265} % magicTransform; }

The operator function can then check input.isconst and use input.t as the actual value.

like image 83
Johannes Schaub - litb Avatar answered Sep 24 '22 10:09

Johannes Schaub - litb