I assume you saw the additional keywords
typename or template used before a name in a template. Me too. Honestly, I was pretty surprised. Today’s post is about dependent names and various template parameters.
Before I write about dependent names, I should write about template parameters.
Template parameters can be types, non-types, and templates.
Okay, types are the most often used template parameters. Here are a few examples:
Non-types can be a
- lvalue reference
- integral type
Integrals are the most used non-types. std::array is the typical example because you have to specify at compile time the size of a std::array:
Templates can be template parameters. In this case, they are called template parameters. The container adaptors std::stack, std::queue, and std::priority_queue use, per default, a std::deque to hold their arguments, but you can use a different container. Their usage is straightforward.
Their definition may look a little bit weird.
Matrix is a simple class template that can be initialized by a std::initializer_list (line 2). A Matrix can be used with a std::vector (line 4 and line 5) or a std::list (line 6) to hold its values. So far, nothing special.
But hold, I forget to mention lines 1 and line 3. Line 1 declares a class template that has two template parameters. Okay, the first parameter is the type of the elements, and the second parameter stands for the container. Look at the second parameter: template <typename, typename> class Cont >. This means the second template argument should be a template requiring two template parameters. The first template parameter is the type of elements the container stores, and the second is the defaulted allocator a container of the standard template library has. Even the allocator has a default value, such as in the case of a std::vector. The allocator depends on the type of elements.
Line 3 shows the usage of the allocator in this internally used container. The matrix can use all containers, which are of the kind: container< type of the elements, allocator of the elements>. This is true for the sequence containers such as std::vector, std::deque, or std::list. std::array and std::forward_list would fail because std::array needs an additional non-type for specifying its size at compile-time, and std::forward_list does not support the size method.
Preparation done. Now, I can write about dependent names.
First of all. What is a dependent name? A dependent name is essentially a name that depends on a template parameter. Let me show what that means. Here are a few examples based on cppreference.com:
Now, the fun starts. A dependent name can be a type, a non-type, or a template parameter. The name lookup is the first difference between non-dependent and dependent names.
- Non-dependent names are looked up at the point of the template definition.
- Dependent names are looked up when the template arguments are known. This means at the point of template instantiation.
If you use a dependent name in a template declaration or template definition, the compiler has no idea whether this name refers to a type, a non-type, or a template parameter. In this case, the compiler assumes that the dependent name refers to a non-type, which may be wrong. This is the case in which you have to help the compiler.
Before I show you two examples, I must add an exception to this rule. You can skip my following few words if you want to get a general idea and jump directly to the following subsection. The exception is: if the name refers in the template definition to the current instantiation, the compiler can deduce the name at the point of the template definition. Here are a few examples:
Finally, I came to the critical idea of my post. If a dependent name could be a type, a non-type, or a template, you have to give the compiler a hint.
Use typename if the Dependent Name is a Type
After such a long introduction, the following program snippet makes it pretty clear.
Without the typename keyword in line 2, the name std::vector<T>::const_iterator in line 2 would be interpreted as a non-type and, consequently, the * stands for multiplication and not for a pointer declaration. Exactly this is happening in line (1).
Similarly, if your dependent name should be a template, you have to give the compiler a hint.
Use .template if the Dependent Name is a Template
Honestly, this syntax looks a little bit weird.
Same story as before. Compare lines 1 and 2. When the compiler reads the name s.func (line 1), it interprets it as non-type. This means the < sign stands for the comparison operator but not opening the square bracket of the template argument of the generic method func. In this case, you must specify that s.func is a template, such as in line 2: s.template func.
Here is the essence of this post in one sentence: When you have a dependent name, use typename to specify that it is a type or .template to specify that it is a template.
The following rules in the C++ Core Guidelines are C-style programming and source files. Let’s see in my next post what this means.