Surprise Included: Inheritance and Member Functions of Class Templates

Contents[Show]

In my last post "Class Templates", I presented the basics about those. 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 method func from class Base. Okay, I have nothing to add to the output of the program.

 inheritance

Making Base a class template totally changes the behavior.

// 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 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 such 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). This instantiation is caused by the call 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, 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) shows 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?

In my next post, I write about alias templates and template parameter.

Bad Marketing

I made a bad marketing job. A few people asked me in the last 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, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, Tornike Porchxidze, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Dimitrov Tsvetomir, Leo Goodstadt, Eduardo Velasquez, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, and Robin Furness.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, and Said Mert Turkal.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmSmall

 

My Newest E-Books

Course: Modern C++ Concurrency in Practice

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

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 192

Yesterday 8036

Week 16407

Month 191117

All 7253407

Currently are 142 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments