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

Add comment


Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 407

All 2293501

Currently are 107 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments