Some confusion in C ++ templates

I am reading this book now, C ++ templates: A Complete Guide. In the paragraph that was stuck, it was not possible to understand the terminology, here are a couple:

The basic principle is that any template argument must be a quantity or value that can be determined at compile time. As it becomes clear that this requirement grows into the dramatic cost of the execution time of template objects. Because template parameters are eventually replaced by compile-time values, they can themselves be used to form compile-time expressions. This was used in the ArrayInClass Template for the size of the member array. The size of the array must be a so-called constant expression, and the template parameter N qualifies as such.

We can push this reasoning a bit further: Because template parameters are compilation objects, 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 that in this example, the name T is both a template parameter and a template argument. Thus, there is a mechanism that allows 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 can not understand anything. I really appreciate any help with simple and clear words.

Edit:

Arrayinclass:

 template <typename T, int N> class ArrayInClass { public: T array[N]; }; 
+4
source share
6 answers

In C ++, there is a certain expression that must be known at compile time. For instance:

 int someArray[30]; 

30 should be a compile-time constant in C ++. It could be:

 int someArray[30 + 3]; 

This is great because the compiler has all the information needed to compile at compile time, like a large array. But:

 void MyFunc(int numItems) { int someArray[30 + numItems]; } 

This is not a compile-time constant, since the user can call MyFunc with any integer value. The compiler does not know how large the array is, so this is a compiler error.

(note: C99 allows the creation of arrays of arbitrary sizes, such as C ++).

Since the template parameter is a compile-time value, it can be passed to other places that require compile-time values:

 template<int ArrayLen> void MyFunc() { int someArray[30 + ArrayLen]; } 

This is legal C ++, because each use of MyFunc should indicate an integer compilation time: the length of the array. You cannot just call MyFunc() , you need to call MyFunc<21>() . And since the template arguments must be determined by compilation time, the user himself cannot provide a value that is not defined for compilation time.

Since template parameters are always defined at compile time, you can nest templates:

 template<int ArrayLen> void OtherFunc { MyFunc<ArrayLen + 3>(); } 

This new template function calls the old one, with an array of 3 more than what was given.

+7
source

When the author says that the template parameters are eventually replaced by compile-time constants, he implies just that. For example, in the following well-known example, the generated code is a numeric constant (i.e., Multiplications occur at compile time, and not at run time:

 #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; } 
+1
source

It is said that in your Dozen<T> class template, the template parameter T also used as the parameter for the member variable, ArrayInClass<T,N> .

So, if you create an instance of Dozen<T> , for example:

 Dozen<float> my_dozen; 

then the compiler will generate the code as follows:

 class Dozen<float> { public: ArrayInClass<float,12> contents; }; 
0
source

The sections in bold say that when you define the class Foo<T> , this T must be known to the compiler at compile time; that is, it cannot be incomplete.

For each different type of T you create an instance of Foo , the compiler creates a completely new type ( Foo<int> , Foo<MyClass> , etc.) that is not related to any other type that you may , created an instance of Foo with, although they may all have the same behavior as defined in the implementation of Foo .

In the example you posted, I assume that ArrayInClass<T,N> creates an array of type T that has N elements.

For instance,

 template< typename T, size_t N > class ArrayInClass { T myArr_[N]; public: // public interface }; 

An array inside the class is not dynamically allocated; it is declared as a static array, although it uses N as the length of the array. This is possible because the compiler will replace the length that you specify as the template parameter when creating the ArrayInClass instance. This would not be possible if the compiler could not determine N at compile time.

0
source

OK, based on your comment, there is a tidbit of reasoning here:

Suppose you have a template:

 template <typename T> struct Moo { }; 

Now we know that, to say Moo<Bar> , Bar must be (a type that is) known at compile time. Not a problem yet.

Now suppose we want to build some class:

 class MyClass { int a; double b; Moo<double> m; }; 

This is also great because Moo<double> is a valid type. Again, no problem. But now let me 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 must be known at compile time. Because of this, the dependent type Moo<U> is replaced by Moo<Zoo> in MyClass<Zoo> , and since Zoo is known, this element type is now also known.

The bold text you are quoting simply says that this reasoning works and you get something real.

For name types, this is not very interesting, because all type names are known at compile time, but template arguments can also be non-names, namely integer values. Now they should also be known at compile time, and you can distribute them in the same way.

0
source

I think it will be very difficult for you to find a clearer expression of what the cited paragraphs say. I think they explain the problem very well.

If you still need help understanding this idea, first try to understand three key words: pattern , parameter, and argument . Here is my definition:

The template parameter is part of the template definition, while the template argument is what is passed to the template to create the template to generate a particular type.

A template is a function that can be parameterized. In your example, Dozen is a template:

 template <typename T> class Dozen { ... }; 

where T is the Dozen parameter. Soon T is a template parameter.

Perhaps a simple analogy will help. Think of a pattern (here it is a Dozen ) as a molding of a sculpture that can be filled with liquid material, which will be installed inside the molded, taking its shape and eventually producing the sculpture. Now the parameter T is similar to a liquid material (rubber, metal, glass, etc.), which will give the character a sculpture. So you can use the same actor to make a series of similar sculptures.

Thus, the hollow cavity in the listing is a parameter T , a placeholder where you put the template arguments, where you fill the template.

So, this is roughly the idea of ​​parameterization in metaprogramming.

Moving to template argument and 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 can recall the function calling another function and the forwarding parameter:

 void foo(int a) { bar(a); } 

foo does not use itself, but passes it as an argument to bar . Similarly, Dozen redirects its own template parameter T as an argument to the ArrayInClass template to create a specific type of this ArrayInClass .

Finally, T is a compile-time expression. This means that it gives value at compile time. (Expressions are functions of a programming language that give meaning). The value of an expression is a type ( T ) or a numeric constant ( 12 ).

ArrayInClass<T,12> also a compile-time expression that gives an instance of the ArrayInClass template that creates the particular type. Soon, a compilation type expression can be used to build another compilation time expression - it produces a different (complex) value.

In metaprogramming, it’s nice not to think of value as a number or a string. Type is also a value here.

0
source

Source: https://habr.com/ru/post/1369050/


All Articles