2

This code is supposed to check if a float equals the half of an int at compile time using template meta-programming: #include

struct A {
    constexpr static bool value = true;
};

struct B {
    constexpr static bool value = false;
};

template<int i>
struct Meta {
    constexpr static int value = i/2;
    constexpr static bool func(float n) {
        return std::conditional_t<n==value,A,B>::value;
    }
};


int main( int argc, const char *argv[] ) {
    constexpr bool b = Meta<4>::func(2);
    std::cout << b << std::endl;
    return 0;
}

But it refuses to compile. The compiler is saying that n is not a constant expression:

test.cpp: In substitution of ‘template<bool _Cond, class _Iftrue, class _Iffalse> using conditional_t = typename std::conditional::type [with bool _Cond = (n == (float)2); _Iftrue = A; _Iffalse = B]’:
test.cpp:15:50:   required from ‘static constexpr int Meta<i>::func(float) [with int i = 4]’
test.cpp:21:36:   required from here
test.cpp:15:50: error: ‘n’ is not a constant expression
         return std::conditional_t<n==value,A,B>::value;
                                                  ^~~~~
test.cpp:15:50: note: in template argument for type ‘bool’ 
test.cpp: In function ‘int main(int, const char**)’:
test.cpp:21:36:   in constexpr expansion of ‘Meta<4>::func((float)2)’
test.cpp:21:38: error: constexpr call flows off the end of the function
     constexpr int b = Meta<4>::func(2);
                                      ^

What's the problem here? The value being passed to Meta::func is a literal value. It should be treated like a constant.

What I'm interested in is how can I perform different actions based on a value at compile time. It should be possible because all the input required to calculate the output is available at compile time.

I want to know how can I perform different actions(which might involve types) based on a value at compile time. It should be possible because all the input required to calculate the output is available at compile time.

saga
  • 1,933
  • 2
  • 17
  • 44
  • 4
    A `constexpr` function is still a regular function that can be called at runtime with an arbitrary `n`. How is the `if constexpr` gonna be handled then?. – StoryTeller - Unslander Monica Mar 19 '18 at 08:24
  • 3
    You use a literal integer value when calling the function, but inside the function it's no longer a literal but a normal variable. The only way to "pass" a literal value is to make `func` a non-type template with `n` as the template argument. Which doesn't work with floating point types. – Some programmer dude Mar 19 '18 at 08:24
  • @StoryTeller as the post mentions, I want the value to be calculated at compile time. I'm trying to understand how template instantiation works with this code. – saga Mar 19 '18 at 08:26
  • 2
    On an unrelated note, you do know that floating-point comparison for equality is mostly useless? – Some programmer dude Mar 19 '18 at 08:26
  • I read what the post says, thank you very much. And it's not about what you want, it's about what a `constexpr` function is versus what it's not. That seems to be the core of your misunderstanding. – StoryTeller - Unslander Monica Mar 19 '18 at 08:26
  • related: https://stackoverflow.com/q/34665079/5470596 – YSC Mar 19 '18 at 08:27
  • @Someprogrammerdude If I declare `float a=3.0/2,b=3.0/2;`, then `a == b` will return true, right? – saga Mar 19 '18 at 08:29
  • @StoryTeller According to `C++ Primer`, a constexpr function is evaluated at compile time if the arguments are constexpr. Shouldn't func in this case be evaluated at compile time? – saga Mar 19 '18 at 08:36
  • And if the arguments aren't? It's still a regular function the runs at run-time. The body of the function must be valid for both. And just like an exception can't be thrown at compile-time, a `constexpr if` can't be used at run-time. – StoryTeller - Unslander Monica Mar 19 '18 at 08:38
  • In that very narrow case, then yes it's probably possible to compare those for equality. But if you do e.g. `a = 3.0 / 2, b = 6.0 / 4` then that will no longer be the case (even if mathematically they are equal). – Some programmer dude Mar 19 '18 at 08:38
  • @StoryTeller But `func` isn't being called with variable arguments anywhere. Is there any kind of "compile time only function" construct in C++? – saga Mar 19 '18 at 08:41
  • @Someprogrammerdude: I believe those, too, should always be equal, under IEEE floating point rules. All source operands can be represented as floating point values, as well as the results. There is no rounding here. – IInspectable Mar 19 '18 at 08:41
  • @saga - Template meta-functions are a compile time only construct. But you can't use floats with them, nor any statements. Biggest misunderstanding about `constexpr` functions is this "run at compile time" business. – StoryTeller - Unslander Monica Mar 19 '18 at 08:42
  • @StoryTeller Well then what can I do to perform this particular check at compile time? – saga Mar 19 '18 at 08:44
  • 1
    @saga Floating point calculations always have the problem of rounding potentially occurring. Doing two different calculations (sometimes even the same calculation with different values) that should mathematically yield the same result might give you two values not *exactly* the same due to rounding effects. This is why you shouldn't compare floating point values for exact equality, but for their difference being small enough... – Aconcagua Mar 19 '18 at 08:45
  • @saga - Trust your compiler. Take a look here at [this disassembly](https://godbolt.org/g/Q7GgXD). I only removed one keyword from your original code. – StoryTeller - Unslander Monica Mar 19 '18 at 08:45
  • @StoryTeller What if the body of `Meat::func` contained just this statement : `return std::conditional_t::value;`, the compiler will give the same error message(`n is not a constexpr`). This task can only be performed at compile time. What can I do to perform such checks at compile time? – saga Mar 19 '18 at 09:03
  • @StoryTeller Edited post to include this. – saga Mar 19 '18 at 09:12

1 Answers1

4

Problem is that there is one single function generated that needs to be callable with both constant expressions (as your literals are) as well as with variably modified values (results of former calculations, for instance).

The constexpr attribute of the function guarantees that the function is evaluated at compile time, if it is possible because of all of the arguments being constant expressions. If any of them is not, the function serves as ordinary function evaluated at runtime. Even if you never use the function this way, it must still be able to handle the case, and thus function arguments in general (n) can never be used as constexpr and in consequence n == value not either; not matter if used in if constexpr or (after your edit) as a template argument.

Actually, however, the inner if constexpr is obsolete anyway: You feed a constexpr function with compile time constants, so the mentioned guarantee applies and your function will be evaluated at compile time, no matter if you use if constexpr or not, thus your function can be simplified to:

constexpr static bool func(float n)
{
    return n == value;
}

Be aware that if this did not come out as expected, you couldn't have assigned the result to a constexpr bool either...

Denoted in the comments to the questions already, but important enough for being hinted to again: Be aware, too, of the rounding problems of floating arithmetics and comparison of such values via exact equality!

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • `Actually, however, the inner if constexpr is obsolete anyway` the example I've provided is a very simple one. It's not supposed to have any practical utility. What I'm interested in is how can I perform different actions based on a value `at compile time`. It should be possible because all the input required to calculate the output is available at compile time. – saga Mar 19 '18 at 09:39
  • The statement in my answer does not change, no matter how complex the function might be, if all of the arguments provided are compile time constants, the function *will* be evaluated at compile time... – Aconcagua Mar 19 '18 at 09:43
  • The value '2', being passed to `Meta::func` as argument is a compile time constant. – saga Mar 19 '18 at 09:45
  • @saga *Yes* it is, thus the function's result *will* be available at compile time. But even if you do not do so, the function *could* be called with the result of e. g. `rand()` (deprecated!), and thus the function parameters cannot be considered compile time constants. – Aconcagua Mar 19 '18 at 09:48
  • `Yes it is, thus the function's result will be available at compile time` No the result isn't available at compile time. This code doesn't compile. What are you trying to say? – saga Mar 19 '18 at 10:14
  • @saga Assuming the function is fixed already such that it does compile (as the sample in my answer), or read it in general terms... – Aconcagua Mar 19 '18 at 11:45
  • Crucial points are: 1. Function parameters are *never* compile time expressions (even if you provide compile time constant arguments for), so you cannot use them where such are required. Defined in C++ standard, you have to live with. 2. Provided your function compiles at all, the function's result *will* be evaluated at compile time if all arguments are compile time constants. – Aconcagua Mar 19 '18 at 11:52
  • So how do I fix the code I provided so that it works correctly? – saga Mar 19 '18 at 11:58
  • 1. Do not use function parameters in expressions where compile time constants are needed. The parameters are *not*. If it is about `if constexpr`, just leave `constexpr`... 2. If you cannot get around compile time expressions at all (e. g. because you need them in template arguments), you will have to pass them as template parameters of the function (not possible with floating point values, though!). 3. If you still rely on 2. *with FP values*, well, I'll have to think something up first... – Aconcagua Mar 19 '18 at 12:06
  • [This](https://stackoverflow.com/a/11518757/1312382) might be of interest for you... Have a look at first comment there, possibly, [std::ratio](http://en.cppreference.com/w/cpp/numeric/ratio/ratio) fits your needs, too (if you need to represent rational numbers only; you'd be out with e. g. π)? – Aconcagua Mar 19 '18 at 12:13