Some confusions in C++ Templates
I am right now reading this book , C++ templates : Complete guide. At a para i got stuck couldn't understand the terminology , here is the para:
A fundamental principle is that any template argument must be a quantity or value that can be determined at compile time. As becomes clear later, this requirement translates into dramatic benefits for the run-time costs of template entities. Because template parameters are eventually subst开发者_开发问答ituted by compile-time values, they can themselves be used to form compile-time expressions. This was exploited in the ArrayInClass template to size the member array array. The size of an array must be a so-called constant-expression, and the template parameter N qualifies as such.
We can push this reasoning a little further: Because template parameters are compile-time entities, they can also be used to create valid template arguments. Here is an example :
template <typename T>
class Dozen {
public:
ArrayInClass<T,12> contents;
};
Note how in this example the name T is both a template parameter and a template argument. Thus, a mechanism is available to enable the construction of more complex templates from simpler ones. Of course, this is not fundamentally different from the mechanisms that allow us to assemble types and functions.
I am unable to understand anything. I appreciate any help with simple and understanding words a lot.
Edit:
Arrayinclass:
template <typename T, int N>
class ArrayInClass {
public:
T array[N];
};
There are certain expression in C++ that are required to be known at compile time. For example:
int someArray[30];
The 30
must be a compile-time constant in C++. It could have been:
int someArray[30 + 3];
That is fine, because the compiler has all of the information necessary to compute at compile time how big to make the array. However:
void MyFunc(int numItems) {
int someArray[30 + numItems];
}
This is not a compile-time constant, since the user could call MyFunc
with any integer value. The compiler does not know how big to make the array, so this is a compiler error.
(note: C99 allows for the creation of arrays of arbitrary sizes like this. C++ does not).
Because a template parameter is a compile-time value, it is possible to pass it to other places that require compile-time values:
template<int ArrayLen> void MyFunc() {
int someArray[30 + ArrayLen];
}
This is legal C++ because every use of MyFunc
must specify a compile-time integer: the length of the array. You cannot just call MyFunc()
you have to call MyFunc<21>()
. And since template arguments must be compile-time determinable values, the user himself cannot provide a value that is not compile-time defined.
Because template parameters are always compile-time defined, you can nest templates:
template<int ArrayLen> void OtherFunc {
MyFunc<ArrayLen + 3>();
}
This new template function calls the old one with an array 3 bigger than what it was given.
When the author says that template parameters are eventually substituted by compile time constants, he means just that. For example, in the following very well known example, the generated code is a numeric constant (i.e. the multiplications take place at compile-time, not runtime:
#include <iostream>
template <int N>
struct Factorial
{
enum { value = N * Factorial<N-1>::value };
};
template <>
struct Factorial<1>
{
enum { value = 1 };
};
// example use
int main()
{
std::cout << Factorial<5>::value << endl;
return 0;
}
It is saying that in your class template, Dozen<T>
, the template parameter T
is also being used as the parameterise the member variable, ArrayInClass<T,N>
.
So if you instantiate Dozen<T>
as e.g.:
Dozen<float> my_dozen;
then the compiler will generate code as follows:
class Dozen<float> {
public:
ArrayInClass<float,12> contents;
};
The sections you've highlighted in bold say that when you define a class Foo<T>
, that T
must be known to the compiler at compile-time; i.e. it cannot be an incomplete type.
For each different type T
that you instantiate Foo
with, the compiler creates a totally new type (Foo<int>
, Foo<MyClass>
etc.) which is unrelated to any of the other types you may have instantiated Foo with, even though they might all have the same behavior as defined by the Foo
implementation.
In the example you've posted, I'm assuming ArrayInClass<T,N>
creates an array of type T
having N
elements.
For instance,
template< typename T, size_t N >
class ArrayInClass
{
T myArr_[N];
public:
// public interface
};
The array within the class is not being dynamically allocated by is being declared as a static array, although it is using N
as the array length. This is possible because the compiler will substitute the length you specify as a template parameter when instantiating ArrayInClass
. It wouldn't have been possible if the compiler was unable to determine N
at compile time.
OK, based on your comment, here's a tidbit of reasoning:
Suppose you have a template:
template <typename T> struct Moo { };
Now we know that to say Moo<Bar>
, Bar
has to be (a type that's) known at compile-time. So far no problem.
Now suppose we want to build some class:
class MyClass
{
int a;
double b;
Moo<double> m;
};
That's also fine, because Moo<double>
is a valid type. Again, no problem. But now let's generalize MyClass and make it a template:
template <typename U>
class MyClass
{
int a;
U b;
Moo<U> m;
};
Now again to say MyClass<Zoo>
, Zoo
has to be known at compile time. Because of this, the dependent type Moo<U>
is substituted to become Moo<Zoo>
in MyClass<Zoo>
, and because Zoo
is known, this member type is now also known.
The boldfaced text that you quote is just saying that this reasoning works and you get something valid.
For typenames, this isn't terribly exciting, because all type names are known at compile time, but template arguments can also be non-typenames, namely integral values. Now those also have to be known at compile time, and you can propagate them in the same way.
I think it will be very hard for you to find our clearer expression of what the cited paragraphs say. They explain the problem very well, I think.
If you still need help to grasp the idea, try to understand the three keywords first: template, parameter and argument. Here is my definition:
A template parameter is part of the template definition, while a template argument is what gets passed to the template to instantiate template in order to generate concrete type.
A template is a feature which can be parametrised. In your example, the Dozen is a template:
template <typename T>
class Dozen
{
...
};
where T is a parameter of Dozen template. Shortly, T is a template parameter.
Perhaps a simple analogy will help. Think of a template (here it's the Dozen) as a sculpture cast which can be filled with liquid material which will set inside the cast adopting to its shape and eventually produce a sculpture. Now, the T parameter is like the liquid material (rubber, metal, glass, etc.) which will give the sculpture specific characters. So, you can use the same cast to make series of similar sculptures.
So, the hollow cavity in cast represents T template parameter, a placeholder where you will put template arguments, where you fill the template.
So, this is roughly the idea of parametrization in metaprogramming.
Moving to template argument and the example with comments:
// T states parameter of Dozen template
template <typename T>
class Dozen
{
// the T is argument used to instantiate concrete type from another template
ArrayInClass<T,12> contents;
};
Here, you may think of a function calling another function and forwarding parameter:
void foo(int a)
{
bar(a);
}
The foo
does not use a itself, but passes it as argument to bar
.
Similarly, Dozen forwards its own template parameter T as argument for ArrayInClass
template to instantiate concrete type of this ArrayInClass
.
Finally, T is compile-time expression. It means, it yields value in compile-time. (Expressions are programming language features that yield a value). The value of the expression is a type (T) or numeric constant (12).
The ArrayInClass<T,12>
is also a compile-time expression which yields instantiation of ArrayInClass
template producing a concrete type. Shortly, compile-type expression can be used to construct another compile-time expression - yield another (complex) value.
In metaprogramming, it's a good idea to not think of value as of number or string. A type is also a value here.
精彩评论