I have a quick question about item 48 in Scott Meyers' "Effective C++". I just don't understand the code copied from the book below,
#include <iostream>
using namespace std;
template <unsigned n>
struct Factorial
{
enum { value=n*Factorial<n-1>::value };
};
template <>
struct Factorial<0>
{
enum { value=1};
};
int main()
{
cout<<Factorial<5>::value<<endl;
cout<<Factorial<10>::value<<endl;
}
Why do I have to use enum in template programming? Is there an alternative way to do this? Thanks for the help in advance.
You could use static const int
also:
template <unsigned n>
struct Factorial
{
static const int value= n * Factorial<n-1>::value;
};
template <>
struct Factorial<0>
{
static const int value= 1;
};
This should be fine also. The result is same in both cases.
Or you could use existing class template, such as std::integral_constant
(in C++11 only) as:
template <unsigned n>
struct Factorial : std::integral_constant<int,n * Factorial<n-1>::value> {};
template <>
struct Factorial<0> : std::integral_constant<int,1> {};
I see that the other answers cover the alternative approaches well, but no one's explained why the enum
(or static const int
) is required.
First, consider the following non-template equivalent:
#include <iostream>
int Factorial(int n)
{
if (n == 0)
return 1;
else
return n * Factorial(n-1);
}
int main()
{
std::cout << Factorial(5) << std::endl;
std::cout << Factorial(10) << std::endl;
}
You should be able to understand it easily. However, it's disadvantage is that the value of the factorial will be computed at run-time, i.e. after running your program the compiler will execute the recursive function calls and calculations.
The idea of template approach is to perform the same calculations at compile-time, and place the result in the resulting executable. In other words, the example you presented resolves to something alike:
int main()
{
std::cout << 120 << std::endl;
std::cout << 3628800 << std::endl;
}
But in order to achieve that, you have to 'trick' the compiler into performing the computations. And in order to do that, you need to let it store the result somewhere.
The enum
is there exactly in order to do that. I will try to explain that by pointing out what would not work there.
If you tried to use a regular int
, it would not work because a non-static member like int
is meaningful only in a instantiated object. And you can't assign a value to it like this but instead do that in a constructor. A plain int
won't work.
You need something that would be accessible on an uninstantiated class instead. You could try static int
but it still doesn't work. clang
would give you a pretty straightforward description of the problem:
c.cxx:6:14: error: non-const static data member must be initialized out of line
static int value=n*Factorial<n-1>::value ;
^ ~~~~~~~~~~~~~~~~~~~~~~~
If you actually put those definitions out-of-line, the code will compile but it will result in two 0
s. That is because this form delays the computation of values to the initialization of program, and it does not guarantee the correct order. It is likely that a Factorial<n-1>::value
s was obtained before being computed, and thus 0
was returned. Additionally, it is still not what we actually want.
Finally, if you put static const int
there, it will work as expected. That's because static const
has to be computed at the compile time, and that's exactly what we want. Let's type the code again:
#include <iostream>
template <unsigned n>
struct Factorial
{
static const int value=n*Factorial<n-1>::value ;
};
template <>
struct Factorial<0>
{
static const int value=1;
};
int main()
{
std::cout << Factorial<5>::value << std::endl;
std::cout << Factorial<10>::value << std::endl;
}
First you instantiate Factorial<5>
; static const int
forces the compiler has to compute its value at compiler time. Effectively, it instantiates the type Factorial<4>
when it has to compute another value. And this goes one until it hit Factorial<0>
where the value can be computed without further instantiations.
So, that was the alternate way and the explanation. I hope it was at least a bit helpful in understanding the code.
You can think of that kind of templates as a replacement of the recursive function I posted at the beginning. You just replace:
return x;
with static const int value = ...
,f(x-1)
with t<x-1>::value
,if (n == 0)
with the specialization struct Factorial<0>
.And for the enum
itself, as it was already pointed out, it was used in the example to enforce the same behavior as static const int
. It is like that because all the enum
values need to be known at compile-time, so effectively every requested value has to be computed at compile-time.
To be more specific, the "enum hack" exists because the more correct way of doing it with static const int
was not supported by many compilers of the time. It's redundant in modern compilers.
You can use static const int
as Nawaz says. I guess the reason Scott Myers uses an enum is that compiler support for in-class initialization of static const integers was a bit limited when he wrote the book. So an enum was a safer option.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With