Проверка на нулевое значение шаблона C ++ не проходит - PullRequest
7 голосов
/ 27 июня 2019

Я изучаю константные выражения в сочетании с шаблонами в C ++ и столкнулся с проблемой, которую не могу понять.

Я хочу проверить, равно ли значение аргумента шаблона (в моем случае целое число без знака) нулю, но компилятор никогда не думает, что значение равно нулю, даже если у меня есть static_assert, которое подтверждает, что оно фактически прошло ниже нуля.

Я реализую простую (или, по крайней мере, я так думал) шаблонную функцию, которая должна просто суммировать все целочисленные значения в диапазоне, например, от От 5 до нуля.

Предполагается выполнять рекурсивный вызов функции шаблона до тех пор, пока она не достигнет нуля, а затем она должна остановиться, но компилятор никогда не думает, что значение параметра шаблона равно нулю.

Вот моя проблемная функция:

    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>();
    }

И он вызывается так:

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

По какой-то причине компилятор никогда не думает, что Value == 0, и, следовательно, он продолжается до тех пор, пока не остановится на static_assert. Если я удалю assert, то компилятор продолжит работу, пока не достигнет максимальной глубины создания:

ошибка: глубина создания шаблона превышает максимум 900 (используйте -ftemplate-deep = для увеличения максимума) Возвращаемое значение == 0? 0: значение + sumAllValues ​​();

Что я делаю не так в вышеуказанной функции? Могу ли я не проверить значение самого параметра шаблона?

Меня вдохновил пример, который я нашел в Википедии:

    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;

См. Ссылку: C ++ 11

И у меня действительно есть рабочий пример, который построен больше так, как приведенный выше пример кода Pow:

    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;
    }

И он вызывается так же, как моя проблемная функция:

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

Он также выполняет рекурсивный вызов функции шаблона, пока не достигнет нуля. Нулевой случай был специализирован для прерывания рекурсии. Этот код работает и выдает значение 15, если я ввожу 5 в качестве аргумента шаблона.

Но я думал, что смогу упростить это с помощью функции, с которой у меня проблемы.

Я занимаюсь разработкой под Linux (Ubuntu 18.04) в Qt 5.12.2.

Обновление:
StoryTeller предлагает рабочее решение, которое использует функцию C ++ 17 "if constexpr" , чтобы остановить рекурсию:

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

        return 0;
    }

Ответы [ 2 ]

7 голосов
/ 27 июня 2019

Создание тела шаблона функции означает создание всего, что он использует.Как выглядит тело sumAllValues<0>?Это примерно так:

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

Видите звонок на sumAllValues<-1>?Хотя он не будет оцениваться, он все же появляется там, и поэтому должен быть создан.Но Value не подписано, так что вы можете обернуться.(unsigned)-1 - очень большое число без знака, не меньше нуля.Таким образом, рекурсия продолжается и может продолжаться бесконечно, если не для реализации, имеющей свои пределы.

Версия со специализацией не имеет одинакового тела функции для sumAllValues<0>, поэтому она никогда не пытается создать экземпляр sumAllValues<-1>.Здесь рекурсия действительно останавливается на 0.

До C ++ 17 специализация, вероятно, является кратчайшим путем для достижения желаемой функциональности.Но с добавлением if constexpr мы можем сократить код до одной функции:

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

    return 0;
}

if constexpr полностью отбросит код в своей ветви, если условие не выполнено.Таким образом, для аргумента 0 рекурсивный вызов не будет присутствовать в теле функции вообще, и поэтому ничего не нужно будет создавать в дальнейшем.

3 голосов
/ 27 июня 2019

В дополнение к StoryTeller ответ :

Интересная деталь о том, как if constexpr работает (инвертируя условие для иллюстрации):

if constexpr(Value == 0)
    return 0;

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

Хотя код после if не будет выполнен, он все еще там и должен быть скомпилирован, и вы попадете в ту же ошибку, что и у вас уже. В отличие от:

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

Теперь, если он находится в ветви else для constexpr if, он снова будет полностью отброшен, если условие соответствует , и мы снова в порядке ...

...