Templates: Misconceptions and Surprises

Contents[Show]

I often teach the basics to templates. Templates are special. Therefore, I encounter many misconceptions which cause surprises. Here are a few of them.

 santa claus 2927962 1280

My first misconception is presumably obvious for many but not for all C++ developers.

First of all, what does related type mean? This is my informal term which stands for types which can be implicitly converted. Here is the starting point.

 

// genericAssignment.cpp

#include <vector>

template <typename T, int N>             // (1)
struct Point{
    Point(std::initializer_list<T> initList): coord(initList){}

    std::vector<T> coord;    
};

int main(){

    Point<int, 3> point1{1, 2, 3};
    Point<int, 3> point2{4, 5, 6};
  
    point1 = point2;                    // (2)
    
    auto doubleValue = 2.2;             
    auto intValue = 2;
    doubleValue = intValue;             // (3)
    
    Point<double, 3> point3{1.1, 2.2, 3.3};
    point3 = point2;                    // (4)

}

 

The class template Point stands for a point in an n-dimensional space. The type of the coordinates and the dimension can be adjusted (line 1). The coordinates are stored in a std::vector<T>. When I create two points with the same coordinate type and dimension, I can assign them.

Now the misconception begins. You can assign an int to a double (line 3). Therefore, it should be possible to assign a Point of ints to a Point of doubles. The C++ compiler is quite specific about line 4. Both class templates are not related and cannot be assigned. They are different types.

genericAssignment

The error message gives the first hint. I need an assignment operator that supports the conversion from Point<int, 3> to Point<double, 3>. The class template now has a generic copy assignment operator.

 

// genericAssignment2.cpp

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

template <typename T, int N>
struct Point{

    Point(std::initializer_list<T> initList): coord(initList){}

    template <typename T2>                        
    Point<T, N>& operator=(const Point<T2, N>& point){     // (1)
        static_assert(std::is_convertible<T2, T>::value, 
                      "Cannot convert source type to destination type!");
        coord.clear();
        coord.insert(coord.begin(), point.coord.begin(), point.coord.end());
        return *this;
    }
    
    std::vector<T> coord;
    
};


int main(){

  Point<double, 3> point1{1.1, 2.2, 3.3};
  Point<int, 3> point2{1, 2, 3};
  
  Point<int, 2> point3{1, 2};
  Point<std::string, 3> point4{"Only", "a", "test"};

  point1 = point2;                                        // (3)
  
  // point2 = point3;                                     // (4)
  // point2 = point4;                                     // (5)

}

 

Due to the line (1), the copy assignment in line (3) works. Let's have a closer look at the class template Point: 

  • Point<T, N>& operator=(const Point<T2, N>& point): The assigned to Point is of type Point<T, N> and accepts only the Point, which has the same dimension but the type could vary: Point<T2, N>.
  • static_assert(std::is_convertible<T2, T>::value, "Cannot convert source type to destination type!"): This expression checks with the help of the function std::is_convertible from the type-traits library, if T2 can be converted to T.

When I use the lines (4) and (5) the compilation fails:

genericAssignment2

Line (3) gives an error because both points have a a different dimension. Line (4) triggers the static_assert in the assignment operator, because a std::string is not convertible to an int.

I assume the next misconception has more surprise potential.

Methods inherited from Class Templates are per se not available

Let's start simple.

// inheritance.cpp

#include <iostream>

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

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

int main(){

    std::cout << std::endl;

    Derived derived;
    derived.callBase();              

    std::cout << std::endl;

}

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

// templateInheritance.cpp

#include <iostream>

template <typename T>
class Base{
public:
    void func(){                    // (1)
        std::cout << "func" << std::endl;
    }
};

template <typename T>
class Derived: public Base<T>{
public:
    void callBase(){
        func();                      // (2)
    }
};

int main(){

    std::cout << std::endl;

    Derived<int> derived;
    derived.callBase();              

    std::cout << std::endl;

}

 

 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. The consequence is that 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.

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()" << std::endl;
  }
  void func2() const {
    std::cout << "func2()" << std::endl;
  }
  void func3() const {
    std::cout << "func3()" << std::endl;
  }
};

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 << std::endl;

  Derived<int> derived;
  derived.callAllBaseFunctions();

  std::cout << std::endl;

}

 

  • 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.
  • Introduce the name into the current scope: The expression using Base<T>::func2 (line 2) introduces func2 into the current scope.
  • Call the name fully qualified: Calling func3 fully qualified (line 3) will break a virtual dispatch and may cause new surprises.

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

templateInheritance2

What's next?

I have more to write about dependent names in my next post. Sometimes you have to disambiguate depenent names with typename or template. If you are seeing this for the first time, you are probably as surprised as me.

 

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, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Wolfgang Gärtner,  Louis St-Amour, Stephan Roslen, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Avi Kohn, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, Sudhakar Balagurusamy, lennonli, and Pramod Tikare Muralidhara.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, and Dendi Suhubdy

 

Seminars

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

Bookable (Online)

Deutsch

English

Standard Seminars 

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

Comments   

0 #1 Pramod 2020-11-05 03:03
In the first post 'Templates of Related Types are not Related', the heading of the post is the truth right(a surprise rather)? not a misconception.
Quote

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

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 7606

Yesterday 8573

Week 16180

Month 174611

All 5043925

Currently are 144 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments