В вашем первом примере базовый тип Fact<0>::result
равен int
. Поскольку аргумент N
вашего базового шаблона также является int
, результат N * Fact<N-1>::result
также равен int
. Это означает, что при N
= 13
результат N * Fact<N-1>::result
переполнит 4-байтовый int
. Целочисленное переполнение со знаком - неопределенное поведение. Именованные enum
значения являются константами времени компиляции, а неопределенное поведение в константных выражениях времени компиляции не допускается, поэтому вы получаете серьезную ошибку.
Во втором примере вы используете не только long
, который потенциально может иметь больший диапазон, чем int
, но long Fact<N>::result = N * Fact<N-1>::result;
не является константным выражением во время компиляции. Это означает, что любое потенциальное неопределенное поведение остается незамеченным во время компиляции. Поведение вашей программы все еще не определено.
Идиоматический c способ сделать факториал времени компиляции в современном C ++ - использовать constexpr
переменные, а не unscoped enum
s:
template<unsigned long N>
struct Fact {
static constexpr unsigned long result = N * Fact<N-1>::result;
};
template<>
struct Fact<0> {
static constexpr unsigned long result = 1;
};
Или функция constexpr
:
constexpr unsigned long fact(unsigned long n)
{
unsigned long ret = 1;
for (unsigned long i = 1; i <= n; ++i) {
ret *= i;
}
return ret;
}
Чтобы сохранить четкое поведение перед лицом переполнения, я оставил элементы result
без знака.
Если вам действительно необходимо сохранить совместимость со старыми компиляторами, вы можете добавить еще одного члена в область с незаданной областью enum
, которая достаточно велика, чтобы его базовый тип был unsigned long
:
template<unsigned long N>
struct Fact {
enum {
result = N * Fact<N-1>::result,
placeholder = ULONG_MAX
};
};
template<>
struct Fact<0> {
enum {
result = 1,
placeholder = ULONG_MAX
};
};
Обратите внимание, что я использовал ULONG_MAX
вместо std::numeric_limits<unsigned long>::max()
, поскольку последнее является только константным выражением времени компиляции в C ++ 11 или более поздних версиях, и единственная причина, по которой я могу использовать этот подход, заключается в поддержании совместимости с pre -C ++ 11 компиляторов.