ClassTemplate

Surprise Included: Inheritance and Member Functions of Class Templates

In my last post, “Class Templates“, I presented the basics. Today, I may surprise you with the inheritance of class templates and the instantiation of member functions of class templates.

 ClassTemplate

 

Here is the first surprise. At least, it was it for me.

Inherited Member Functions of Class Templates are not Available

Let’s start simple.

// inheritance.cpp

#include <iostream>

class Base{
public:
    void func(){                    // (1)
        std::cout << "func\n";
    }
};

class Derived: public Base{
public:
    void callBase(){
        func();                      // (2)
    }
};

int main(){

    std::cout << '\n';

    Derived derived;
    derived.callBase();              

    std::cout << '\n';

}

 

I implemented a class Base and Derived. Derived is public derived from Base and can, therefore, be used in its method callBase (line 2), the member function func from class Base. Okay, I have nothing to add to the output of the program.

 inheritance

Making Base a class template changes the behavior.

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.

     

    // templateInheritance.cpp
    
    #include <iostream>
    
    template <typename T>
    class Base{
    public:
        void func(){                    // (1)
            std::cout << "func\n";
        }
    };
    
    template <typename T>
    class Derived: public Base<T>{
    public:
        void callBase(){
            func();                      // (2)
        }
    };
    
    int main(){
    
        std::cout << '\n';
    
        Derived<int> derived;
        derived.callBase();              
    
        std::cout << '\n';
    
    }
    

     

    I assume the compiler error may surprise you.

    templateInheritance1

    The line “there are no arguments to ‘func’ that depend on a template parameter, so a declaration of ‘func’ must be available” from the error message gives the first hint. func is a so-called non-dependent name because its name does not depend on the template parameter T. Non-dependent names are looked up and bound at the point of the template definition. Consequently, the compiler does not look in the from T dependent base class Base<T>, and there is no name func available outside the class template. Only dependent names are looked up and bound at the point of template instantiation.

    This process is called Two Phase Lookup. The first phase is, in particular, responsible for looking up non-dependent names; the second phase is responsible for looking up dependent names.

    There are three workarounds to extend the name lookup to the dependent base class. The following example uses all three.

     

    // templateInheritance2.cpp
    
    #include <iostream>
    
    template <typename T>
    class Base{
    public:
      void func1() const {
        std::cout << "func1()\n";
      }
      void func2() const {
        std::cout << "func2()\n";
      }
      void func3() const {
        std::cout << "func3()\n";
      }
    };
    
    template <typename T>
    class Derived: public Base<T>{
    public:
      using Base<T>::func2;              // (2)
      void callAllBaseFunctions(){
    
        this->func1();                   // (1)
        func2();                         // (2)
        Base<T>::func3();                // (3)
    
      }
    };
    
    
    int main(){
    
      std::cout << '\n';
    
      Derived<int> derived;
      derived.callAllBaseFunctions();
    
      std::cout << '\n';
    
    }
    

     

    1. Make the name dependent: The call this->func1 in line 1 is dependent because this is implicit dependent. The name lookup will consider, in this case, all base classes.
    2. Introduce the name into the current scope: The expression using Base<T>::func2 (line 2) introduces func2 into the current scope.
    3. Call the name fully qualified: Calling func3 fully qualified (line 3) will break a virtual dispatch and may cause new surprises.

    Which option should you use? In general, I prefer the first option making func1 dependent: this->func1. This solution does even work when you rename your base class. 

    In the end, here is the output of the program.

    templateInheritance2

    Instantiation of Member Functions is Lazy

    Lazy means the instantiation of a member function of a class template happens only when needed. Proof? Here we are.

    // lazy.cpp
    
    #include <iostream>
    
    template<class T> 
    struct Lazy{
        void func() { std::cout << "func\n"; }
        void func2(); // not defined (1)
    };
    
    int main(){
      
      std::cout << '\n';
        
      Lazy<int> lazy;
      lazy.func();
      
      std::cout << '\n';
        
    }
    

     

    Although the method func2 () (1) of class Lazy is only declared but not defined, the compiler accepts the program. Because func2, a definition of the member function is not necessary.

    lazy

    This laziness of the instantiation process of member functions has two interesting properties.

    Save Resources

    When you instantiate, for example, a class template  Array2 for various types, only the used member functions are instantiated. This laziness does not hold for a non-template class Array1. Let me show you an example on C++ Insights.

    // lazyInstantiation.cpp
    
    #include <cstddef> 
    
    class Array1 { 
     public: 
        int getSize() const { 
          return 10; 
     } 
     private: 
        int elem[10]; 
    };
    
    template <typename T, std::size_t N> 
    class Array2 { 
     public: 
        std::size_t getSize() const {
            return N;
        }
      private: 
         T elem[N]; 
    }; 
    
    
    int main() {
    
        Array1 arr;
        
        Array2<int, 5> myArr1;
        Array2<double, 5> myArr2;   // (1) 
        myArr2.getSize();           // (2) 
    
    }
    

     

     The member function getSize() of the class template Array2 is only instantiated for myArr2 (1). The call causes this instantiation myArr2.getSize() (2).

     C++ Insights shows the truth. The crucial lines in the following screenshot are lines 40 and 59.

    lazyInstantiation

    Partial Usage of Class Templates

    You can instantiate class templates with template arguments that do not support all member functions. When you don’t call those member functions, all is fine.

    // classTemplatePartial.cpp
    
    #include <iostream>
    #include <vector>
    
    template <typename T>         // (1) 
    class Matrix {
     public:
        explicit Matrix(std::initializer_list<T> inList): data(inList) {}
        void printAll() const {   // (2)
            for (const auto& d: data) std::cout << d << " ";
        }
    private:
        std::vector<T> data;
    };
    
    int main() {
    
        std::cout << '\n';
    
        const Matrix<int> myMatrix1({1, 2, 3, 4, 5});
        myMatrix1.printAll();   // (3) 
    
        std::cout << "\n\n";
    
        const Matrix<int> myMatrix2({10, 11, 12, 13});
        myMatrix2.printAll();  // (4) 
    
         std::cout << "\n\n";     
    const Matrix<Matrix<int>> myMatrix3({myMatrix1, myMatrix2}); // myMatrix3.printAll(); ERROR (5) }

     

    The class template Matrix (1) is intentionally simple. It has a type parameter T, that holds its data in a std::vector, and can be initialized by a std::initalizer_list. Matrix supports the member function printAll() to display all its members. (3) and (4) show its usage. The output operator is not overloaded for Matrix Consequently, I can create myMatrix3 having other Matrix objects as members, but I cannot display them.

     classTemplatePartial

     

    Enabling line 5 causes a pretty verbose error message of about 274 lines.

     classTemplatePartialError

    What’s next?

    I will write about alias templates and template parameters in my next post.

    Bad Marketing

    I did a bad marketing job. A few people asked me in the last few days if my C++20 book, published on LeanPub, is available in physical form. Sure, since one month. Choose your preferred Amazon Marketplace.

     

     

    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 *