optical illusion 311130 1280

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

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.

 

 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 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:

Rainer D 6 P2 500x500

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

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

    Templates

    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.

     

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

    templateTemplateParameters

    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.

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

     

    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::liststd::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.

    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 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:

     

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

     

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

     

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

    What’s next?

    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.

     

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *