Подтвердить что ты не робот

С++ 14: выводимые (авто) возвращаемые типы из constexpr с тернарными выражениями

Я экспериментирую с функциями constexpr в С++ 14. Следующий код, который вычисляет факториал, работает как ожидалось:

template <typename T>
constexpr auto fact(T a) {
    if(a==1)
        return 1;
    return a*fact(a-1);
}

int main(void) {
    static_assert(fact(3)==6,  "fact doesn't work");
}

когда он компилируется следующим образом с помощью clang:

> clang++ --version
clang version 3.5.0 (tags/RELEASE_350/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix
> clang++ -std=c++14 constexpr.cpp

Однако, когда я изменяю определение fact для использования тройного оператора ?:

template <typename T>
constexpr auto fact(T a) {
    return a==1 ? 1 : a*fact(a-1);
}

Я получаю следующую ошибку компилятора:

> clang++ -std=c++14 constexpr.cpp
constexpr.cpp:12:31: fatal error: recursive template instantiation exceeded maximum depth of
      256
    return a==T(1) ? T(1) : a*fact(a-1);
        ... snip ...
constexpr.cpp:16:19: note: in instantiation of function template specialization 'fact<int>'
      requested here
    static_assert(fact(3)==6,  "fact doesn't work");

Проблема исправлена, если я явно укажу тип возврата T (вместо того, чтобы использовать auto для вывода возвращаемого типа)

template <typename T>
constexpr T fact(T a) {
    return a==1 ? 1 : a*fact(a-1);
}

Если я удалю параметр шаблона, шаблон повторяется (тернарная версия терпит неудачу и работает версия if)

// this works just fine
constexpr auto fact(int a) {
    if(a==1)
        return 1;
    return a*fact(a-1);
}

тогда как это не работает

constexpr auto fact(int a) {
    return a==1 ? 1 : a*fact(a-1);
}

со следующей ошибкой

> clang++ -std=c++14 constexpr.cpp
constexpr.cpp:16:25: error: function 'fact' with deduced return type cannot be used before it
      is defined
    return a==1 ? 1 : a*fact(a-1);
                        ^
constexpr.cpp:15:16: note: 'fact' declared here
constexpr auto fact(int a) {
               ^
constexpr.cpp:20:26: error: invalid operands to binary expression ('void' and 'int')
    static_assert(fact(3)==6,  "fact doesn't work");
                  ~~~~~~~^ ~
2 errors generated.

Что здесь происходит?

4b9b3361

Ответ 1

Получаемый тип оценки тройного выражения является общим типом его второго и третьего аргументов.

Если компилятор выводит тип возвращаемого значения, вы вынуждаете его оценивать оба этих аргумента для тернарного выражения. Это означает, что рекурсия не заканчивается даже при достижении условия завершения, потому что, когда a==1, чтобы выяснить тип возврата fact(0), компилятор должен продолжить оценивать дальнейшие рекурсивные вызовы на fact, и наступает бесконечная рекурсия.

Указывая тип возврата, fact(0) не нужно оценивать, когда a==1, и рекурсия может завершиться.


Как и в случае с двумя операторами return, соответствующее стандартное предложение - —

(из N4296) §7.1.6.4/9 [dcl.spec.auto]

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

В вашем примере в вызове fact<int>(1) тип возвращаемого значения, выводимый из первого оператора return, равен int, поэтому возвращаемый тип fact<int>(0) во втором return выражении не может быть чем-то иным, кроме int. Это означает, что компилятору не нужно оценивать тело fact<int>(0), и рекурсия может завершиться.

Действительно, если вы принудительно оцениваете вызов на fact во втором выражении return, например, изменив первый пример, чтобы T был аргументом шаблона непигового типа

template <unsigned T>
constexpr auto fact() {
    if(T==1)
        return 1;
    return T*fact<T-1>();
}

clang не работает с ошибкой

фатальная ошибка: рекурсивный экземпляр шаблона превысил максимальную глубину 256

Живая демонстрация