Types-, Non-Types, and Templates as Template Parameters

Contents[Show]

I assume you saw the additional keywords typename or template used before a name in a template. Me too. Honestly, I was quite surprised. Today's post is about dependent names and various template parameters.

 optical illusion 311130 1280

Before I write about dependent names, I should write about template parameters.

Template Parameter

Template parameters can be types, non-types, and templates.

Types

Okay, types are the most often used template parameters. Here are a few examples:

std::vector<int> myVec;
std::map<std::string, int> myMap;
std::lock_guard<std::mutex> myLockGuard;

Non-Types

Non-types can be a

  • lvalue reference
  • nullptr
  • pointer
  • enumerator
  • integral

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:

std::array<int, 3> myArray{1, 2, 3};

Templates

Templates can be template parameters. In this case, they are called template 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.

 

std::stack<int> stack1;
stack1.push(5);
    
std::stack<double, std::vector<double>> stack2;
stack2.push(10.5);

 

Their definition may look a little bit weird.

 

// templateTemplateParameters.cpp

#include <iostream>
#include <list>
#include <vector>
#include <string>

template <typename T, template <typename, typename> class Cont >   // (1)
class Matrix{
public:
  explicit Matrix(std::initializer_list<T> inList): data(inList){  // (2)
    for (auto d: data) std::cout << d << " ";
  }
  int getSize() const{
    return data.size();
  }

private:
  Cont<T, std::allocator<T>> data;                                 // (3)                               

};

int main(){

  std::cout << std::endl;

                                                                    // (4)
  Matrix<int, std::vector> myIntVec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 
  std::cout << std::endl;
  std::cout << "myIntVec.getSize(): " << myIntVec.getSize() << std::endl;

  std::cout << std::endl;

  Matrix<double, std::vector> myDoubleVec{1.1, 2.2, 3.3, 4.4, 5.5}; // (5)
  std::cout << std::endl;
  std::cout << "myDoubleVec.getSize(): "  << myDoubleVec.getSize() << std::endl;

  std::cout << std::endl;
                                                                    // (6)
  Matrix<std::string, std::list> myStringList{"one", "two", "three", "four"};  
  std::cout << std::endl;
  std::cout << "myStringList.getSize(): " << myStringList.getSize() << std::endl;

  std::cout << std::endl;

}

 

Matrix is a simple class template, that can be initialised 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. 

templateTemplateParameters

But hold, I forget to mention line 1 and line 3. Line 1 declares a class template which has two template parameters. Okay, the first parameter is the type of the elements and the second parameter stands for the container. Let's have a closer 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 the elements the container stores and the second template parameter is the defaulted allocator a container of the standard template library has. Even the allocator has a default value such as in case of a std::vector. The allocator depend on the type of the elements.

template<
    class T,
    class Allocator = std::allocator<T>
> class vector;

 

Line 3 shows the usage of the allocator in this internally used container. 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::liststd::array and std::forward_list would fail, because std::array needs an additional non-type for specifying its size at compiletime and std::forward_list does not support the size method.

Preperation done. Now, I can write about dependent names.

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:

 

template<typename T>
struct X : B<T> // "B<T>" is dependent on T
{
    typename T::A* pa; // "T::A" is dependent on T
    void f(B<T>* pb) {
        static int i = B<T>::i; // "B<T>::i" is dependent on T
        pb->j++; // "pb->j" is dependent on T
    }
};

 

Now, the fun starts. A dependent name can be a type, a non-type, or a template 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 lookup 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 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 have to add an exception to this rule. You can skip my next few words if you want to get the general idea and jump directly to the next subsection. The exception is: if the name referers 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:

 

template <class T> class A {
    A* p1;    // A is the current instantiation
    A<T>* p2; // A<T> is the current instantiation
    ::A<T>* p4; // ::A<T> is the current instantiation
    A<T*> p3; // A<T*> is not the current instantiation
};
template <class T> class A<T*> {
    A<T*>* p1;  // A<T*> is the current instantiation
    A<T>* p2;   // A<T> is not the current instantiation
};
template <int I> struct B {
    static const int my_I = I;
    static const int my_I2 = I+0;
    static const int my_I3 = my_I;
    B<my_I>* b3;  // B<my_I> is the current instantiation
    B<my_I2>* b4; // B<my_I2> is not the current instantiation
    B<my_I3>* b5; // B<my_I3> is the current instantiation
};

 

Finally, I come 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 next program snippet makes it pretty clear.

 

template <typename T>
void test(){
    std::vector<T>::const_iterator* p1;          // (1)
    typename std::vector<T>::const_iterator* p2; // (2)
}

 

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 in this case for multiplication and not for a pointer declaration. Exactly this is happening in line (1).

Similar, 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.

 

template<typename T>
struct S{
    template <typename U> void func(){}
}
template<typename T>
void func2(){
    S<T> s;
    s.func<T>();             // (1)
    s.template func<T>();    // (2)
}

 

Same story as before. Compare the lines 1 and 2. When the compiler reads the name s.func (line 1) it decides to interpret it as non-type. This means the < sign stands for the comparison operator but not opening square bracket of the template argument of the generic method func. In this case, you have to specify that s.func is 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 specifiy that it is a type or .template to specifiy that it is a template.

What's next?

The next rules in the C++ Core Guidlinees are about C-style programming and source files. Let's see in my next post what this means.

 

 

Thanks a lot to my Patreon Supporters: Paul Baxter,  Meeting C++, Matt Braun, Avi Lachmish, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Dilettant, Marko, Ramesh Jangama, G Prvulovic, and Reiner Eiteljörge.

Thanks in particular to:  TakeUpCode 450 60

 

Get your e-book at Leanpub:

The C++ Standard Library

 

Concurrency With Modern C++

 

Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight in the current and the upcoming concurrency in C++. This insight includes the theory and a lot of practice with more the 100 source files.

 

Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 600 pages full of modern C++ and more than 100 source files presenting concurrency in practice.

 

Get your interactive course

 

Modern C++ Concurrency in Practice

C++ Standard Library including C++14 & C++17

educative CLibrary

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Runs in the browser
  • 78 code snippets
  • 55 illustrations

Based on my book "The C++ Standard Library" educative.io created an interactive course.

What's Inside?

  • 149 lessons
  • 111 code playgrounds => Runs in the browser
  • 164 code snippets
  • 25 illustrations
Tags: templates

Add comment


Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 1553

All 1724642

Currently are 224 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments