Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check for C++ template value zero fails

I'm exploring constant expressions in combination with templates in C++, and I've run into a problem I cannot understand.

I want to check if the value of a template argument (in my case an unsigned int) is zero, but the compiler never thinks the value is zero even though I have a static_assert that confirms that it has actually passed below zero.

I'm implementing a simple (or at least I thought so) template function that is supposed to just summarize all integer values in the range from e.g. 5 down to zero.

It's supposed to perform a recursive invocation of the template function until it reaches zero, and then it should stop, but the compiler never thinks the template parameter value is zero.

Here's my problematic function:

    template <unsigned int Value>
    constexpr unsigned int sumAllValues()
    {
        static_assert (Value >= 0, "Value is less than zero!");
        return Value == 0 ? 0 : Value + sumAllValues<Value - 1>();
    }

And it's invoked like this:

    constexpr unsigned int sumVals = sumAllValues<5>();

For some reason the compiler never thinks Value == 0, and hence it continues until it stops on the static_assert. If I remove the assert the compiler continues until it reaches the max instantiation depth:

error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) return Value == 0 ? 0 : Value + sumAllValues();

What am I doing wrong in the above function? Can I not check the value of the template parameter itself?

I was inspired by an example I found on Wikipedia:

    template<int B, int N>
    struct Pow
    {
        // recursive call and recombination.
        enum{ value = B*Pow<B, N-1>::value };
    };

    template< int B >
    struct Pow<B, 0>
    {
        // ''N == 0'' condition of termination.
        enum{ value = 1 };
    };

    int quartic_of_three = Pow<3, 4>::value;

See reference: C++11

And I actually do have a working example which is constructed more in the way the above Pow example code is:

    template <unsigned int Value>
    constexpr unsigned int sumAllValues()
    {
        static_assert (Value > 0, "Value too small!");
        return Value + sumAllValues<Value - 1>();
    }

    template <>
    constexpr unsigned int sumAllValues<0>()
    {
        return 0;
    }

And it's invoked in the same way as my problematic function:

    constexpr unsigned int sumVals = sumAllValues<5>();

It also performs a recursive invocation of the template function until it reaches zero. The zero case has been specialized to interrupt the recursion. This code works, and produces the value 15 if I input 5 as template argument.

But I thought I could simplify it with the function I'm having problems with.

I'm developing on Linux (Ubuntu 18.04) in Qt 5.12.2.

Update:
StoryTeller suggests a working solution which is to use the C++17 feature "if constexpr" to halt the recursion:

    template <unsigned int Value>
    constexpr unsigned int sumAllValues()
    {
        if constexpr (Value > 0)
            return Value + sumAllValues<Value - 1>()

        return 0;
    }
like image 464
c4m3lc4s3 Avatar asked Jun 27 '19 05:06

c4m3lc4s3


2 Answers

Instantiating the body of a function template means instantiating everything it uses. How does the body of sumAllValues<0> look like? It's something like this:

template <>
constexpr unsigned int sumAllValues<0>()
{
    static_assert (0 >= 0, "Value is less than zero!");
    return Value == 0 ? 0 : 0 + sumAllValues<0 - 1>();
}

See the call to sumAllValues<-1>? While it's not going to be evaluated, it still appears there and must therefore be instantiated. But Value is unsigned, so you get wrap around. (unsigned)-1 is a very large unsigned number, not something less than zero. So the recursion continues, and it may continue indefinitely if not for the implementation having its limits.

The version with the specialization doesn't have the same function body for sumAllValues<0>, so it never tries to instantiate sumAllValues<-1>. The recursion really stops at 0 there.

Prior to C++17, the specialization is probably the shortest way to get at the functionality you want. But with the addition of if constexpr, we can reduce the code to one function:

template <unsigned int Value>
constexpr unsigned int sumAllValues()
{
    if constexpr (Value > 0)
      return Value + sumAllValues<Value - 1>()

    return 0;
}

if constexpr will entirely discard the code in its branch if the condition isn't met. So for the 0 argument, there will not be a recursive call present in the body of the function at all, and so nothing will need to be instantiated further.

like image 98
StoryTeller - Unslander Monica Avatar answered Oct 23 '22 05:10

StoryTeller - Unslander Monica


In addition to StoryTeller's answer:

An interesting detail on how if constexpr works (inverting the condition for illustration):

if constexpr(Value == 0)
    return 0;

return Value + sumAllValues<Value - 1>();

While the code after the if won't be executed, it is still there and must be compiled, and you fall into the same error you had already. In contrast to:

if constexpr(Value == 0)
    return 0;
else
    return Value + sumAllValues<Value - 1>();

Now, as residing in the else branch to the constexpr if, it will again be entirely discarded if the condition does match and we are fine again...

like image 35
Aconcagua Avatar answered Oct 23 '22 03:10

Aconcagua