11

A few days ago I asked by which criteria the compiler decides whether or not, to compute a constexpr function during compile time.

When does a constexpr function get evaluated at compile time?

As it turns out, a constexpr is only evaluated during compile-time, if all parameters are constant expressions and the variable you are assigning it to is are constant expression as well.

template<typename base_t, typename expo_t>
constexpr base_t POW(base_t base, expo_t expo)
{
    return (expo != 0 )? base * POW(base, expo -1) : 1;
}

template<typename T>
void foobar(T val)
{
    std::cout << val << std::endl;
}

int main(int argc, char** argv)
{
    foobar(POW((unsigned long long)2, 63));
    return 0;
}

If what I was told is true, this code example is very impratical, since foobar doesn't take a constexpr (you can't use consexpr for parameters for some reason), POW gets evaluated during runtime, even though it would have been possible to compute it during compile-time. The obvious solution for forcing a compile-time evaluation would be this:

auto expr = POW((unsigned long long)2, 63);
foobar(expr);

This however forces me to use an additional line of code, which shouldn't be necessary each time I want to make sure a constexpr gets evaluated during compile-time. To make this a little more convenient, I've come up with the following dubious macro:

#define FORCE_CT_EVAL(func) [](){constexpr auto ___expr = func; return std::move(___expr);}()
foobar(FORCE_CT_EVAL(POW((unsigned long long)2, 63)));

Despite the fact that it works just fine, I feel like as if something isn't right about it. Does creating an anonymous lambda impact performance? Does returning by rvalue reference actually move the expression to the function parameter? How does std::move impact performance? Is there a better one liner solution for this?

Community
  • 1
  • 1
Byzantian
  • 3,208
  • 4
  • 27
  • 31
  • What's wrong with Pubby's template trick in his answer to your previous question? – Mat Jan 12 '13 at 14:53
  • @Mat I am looking for a generic solution. I don't want to write a template wrapper for every function. – Byzantian Jan 12 '13 at 15:05
  • 2
    Ah, got it. (And I have no idea.) (Irrelevant remark: `2ULL` would save you some typing.) – Mat Jan 12 '13 at 15:27
  • that lambda doesn't need std::move at return because ___expr is a `xvalue`, also... I don't see it working fine if you pass variables, not literals, as arguments, since the lambda would need to capture them. The way it is would issue an error about variables not being able to be captured, not much suggestive about compilation eval enforcement. – oblitum Jan 12 '13 at 17:20
  • Also, a better one liner was already provided at the former question through `std::integral_constant`. – oblitum Jan 12 '13 at 17:42
  • @chico wouldn't decltype() impact performance in this case? – Byzantian Jan 12 '13 at 17:52
  • 1
    I think a better macro would be, `#define COMPILATION_EVAL(f, ...) (std::integral_constant::value)` – oblitum Jan 12 '13 at 17:57
  • I guess you mean about compilation time performance, I guess it would have minimal impact compared to what you're trying to calculate with the constexpr function. – oblitum Jan 12 '13 at 18:00
  • That macro would issue a proper error when passing variables, not literals, and when passing wrong number of params. It would be used as `COMPILATION_EVAL(POW, 2, 63)`. – oblitum Jan 12 '13 at 18:02
  • @chico Thanks. But why the variadic parameters on the macro? What's so wrong about passing a variable instead? – Byzantian Jan 12 '13 at 18:09
  • @cyberpunk_, it's generic that way, can be used with any constexpr function. – oblitum Jan 12 '13 at 18:27
  • @chico But this would work, too, wouldn't it? `#define COMPILATION_EVAL(f) (std::integral_constant::value)` Why go though the hassle of using variadic parameters? – Byzantian Jan 12 '13 at 18:43
  • @cyberpunk_, how that would work, there're multiple issues there. `decltype(f`) is the type of a function, you don't return that, `value` would be of that type, `f` alone also, is not calling anything. – oblitum Jan 12 '13 at 18:47
  • @cyberpunk_, oh now I got it... `#define COMPILATION_EVAL(f) (std::integral_constant::value)` and use it as `COMPILATION_EVAL(POW(2, 63))`. OK =) – oblitum Jan 12 '13 at 18:50
  • But if I pass something like COMPILATION_EVAL(foobar(42)) would the macro generate code like this? `(std::integral_constant::value)` That's perfectly valid. EDIT: Beat me to it. – Byzantian Jan 12 '13 at 18:51
  • 1
    Note that in practice, with -O2, clang evaluates the code in the question at compile-time. And if you split it into 2 lines: `const auto p=POW((unsigned long long)2, 63); foobar(p);`, gcc does as well. – Marc Glisse Jan 14 '13 at 01:22

1 Answers1

5

Just to not leave it buried in comments:

#include <type_traits>

#define COMPILATION_EVAL(e) (std::integral_constant<decltype(e), e>::value)

constexpr int f(int i){return i;}

int main()
{
    int x = COMPILATION_EVAL(f(0));
}

EDIT1:

One caveat with this approach, constexpr functions can accept floating-point and be assigned to constexpr floating-point variables, but you cannot use a floating-point type as a non-type template parameter. Also, same limitations for other kinds of literals.

Your lambda would work for that, but I guess you would need a default-capture to get meaningful error message when non-constexpr stuff get passed to the function. That ending std::move is dispensable.

EDIT2:

Err, your lambda approach doesn't work for me, I just realized, how it can even work, the lambda is not a constexpr function. It should not be working for you too.

It seems there's really no way around it but initializing a constexpr variable in local scope.

EDIT3:

Oh, ok, my bad, the purpose of the lambda is just the evaluation. So it's working for that. Its result, instead, is which is unusable to follow another compilation time eval.

EDIT4:

On C++17, lambdas now can be used in constexpr contexts, so the limitation referred to in EDIT2/EDIT3 is removed! So the lambda solution is the correct one. See this comment for more information.

oblitum
  • 11,380
  • 6
  • 54
  • 120
  • 2
    Lambda doesn't need to be `constexpr`. `COMPILATION_EVAL` doesn't need to be used in `constexpr`. It is working. – zch Jan 12 '13 at 19:57
  • `constexpr auto a = 42.2; auto res = FORCE_CT_EVAL(POW(a, 2)); std::cout << res << std::endl;` Works just fine under GCC 4.7. Which compiler are you using, chico? Since a is known at compile time, the lambda doesn't have to capture it. – Byzantian Jan 12 '13 at 19:59
  • @cyberpunk_ "auto res"? try assigning a constexpr variable. – oblitum Jan 12 '13 at 20:01
  • @zch I guess you didn't get the point of the question, see his previous question for reference. It's about ensuring compilation time eval of a `constexpr` function. – oblitum Jan 12 '13 at 20:02
  • @chico I can't because the lambda itself is not a constexpr. This is of no concern however, because the constexpr value we want to force is assigned within the lambda. We could solve this if there was a way to pass a function to a template. – Byzantian Jan 12 '13 at 20:04
  • @chico it would still be nice if we could find a way to evaluate the lambda during compile-time as well. – Byzantian Jan 12 '13 at 20:11