C++ Core Guidelines: Type Erasure

Contents[Show]

,Rule "T.5: Combine generic and OO techniques to amplify their strengths, not their costs"  of the core guidelines to generic programming uses type erasure as an example. Type erasure? Really! Of course, it takes me two posts to explain this advanced template technique.

fear

First of all: What does type erasure mean?

  • Type Erasure: Type Erasure enables you to use various concrete types through a single generic interface.

Of course, you already quite often used type erasure in C++ or C. The C-ish way of type erasure is a void pointer; the C++-ish way of type erasure is object-orientation. Let's start with a void pointer.

Void Pointer

Let's have a closer look at the declaration of std::qsort:

void qsort(void *ptr, std::size_t count, std::size_t size, cmp);

 

with:

int cmp(const void *a, const void *b);

 

The comparison function cmp should return a

  • negative integer: the first argument is less than the second
  • zero: both arguments are equal
  • positive integer: the first argument is greater than the second

Thanks to the void pointer, std::qsort is generally applicable but also quite error-prone.

Maybe you want to sort a std::vector<int>, but you used a comparator for C-strings. The compiler can not catch this error because the type information was removed. You end with undefined behaviour.

In C++ we can do better:

Object Orientation

Here is a simple example, which serves as a starting point for further variations.

 

// typeErasureOO.cpp

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

struct BaseClass{                                       // (2)
	virtual std::string getName() const = 0;
};

struct Bar: BaseClass{                                  // (4)
	std::string getName() const override {
	    return "Bar";
	}
};

struct Foo: BaseClass{                                  // (4)
	std::string getName() const override{
	    return "Foo";
	}
};

void printName(std::vector<const BaseClass*> vec){      // (3)
    for (auto v: vec) std::cout << v->getName() << std::endl;
}


int main(){
	
	std::cout << std::endl;
	
	Foo foo;
	Bar bar; 
	
	std::vector<const BaseClass*> vec{&foo, &bar};   // (1)
	
	printName(vec);
	
	std::cout << std::endl;

}

std::vector<const Base*> (1) has a pointer to a constant BaseClasses. BaseClass is abstract Base Class, which is used in (3). Foo and Bar (4) are the concrete classes.

The output of the program is not so thrilling.

 typeErasureOO

To say it more formally. Foo and Bar implement the interface of the BaseClass and can, therefore, be used instead of BaseClass. This principle is called Liskov substitution principle and is type erasure in OO.

In Object Orientated  Programming you implement an interface. In dynamically typed languages such as Python, you are not interested in interfaces you are interested in behaviour.

Templates

Let me make a short detour.

snake 312561 1280

In Python, you care about behaviour and not about formal interfaces. This idea is well-known as duck typing. To make it short, the expression goes back to the poem from James Whitcomb Rileys: Here it is:

 

“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”

What does that mean? Imagine a function acceptOnlyDucks that only accepts ducks as an argument. In statically typed languages such as C++, all types which are derived from Duck can be used to invoke the function. In Python, all types, which behave like Duck's, can be used to invoke the function. To make it more concrete. If a bird behaves like a Duck it is a Duck. There is often a proverb used in Python to describe this behaviour quite good.

Don't ask for permission, ask for forgiveness.

In case of our Duck, this means, that you invoke the function acceptsOnlyDucks with a bird and hope for the best. If something bad happens, you catch the exception with an except clause. Often this strategy works very well and very fast in Python.

Okay, this is the end of my detour. Maybe you wonder, why I wrote about duck typing in this C++ post. The reason is quite straightforward. Thanks to templates, we have duck typing in C++. When you combine duck typing together with OO, it becomes even type safe.

std::function as a polymorphic function wrapper is a nice example to type erasure in C++.

std::function

std::function can accept everything, which behaves like a function. To be more precise. This can be any callable such as a function, a function object, a function object created by std::bind, or just a lambda function.

// callable.cpp

#include <cmath>
#include <functional>
#include <iostream>
#include <map>

double add(double a, double b){
	return a + b;
}

struct Sub{
	double operator()(double a, double b){
		return a - b;
	}
};

double multThree(double a, double b, double c){
	return a * b * c;
}

int main(){
    
    using namespace std::placeholders;

    std::cout << std::endl;

    std::map<const char , std::function<double(double, double)>> dispTable{  // (1)
        {'+', add },                                         // (2)
        {'-', Sub() },                                       // (3)
        {'*', std::bind(multThree, 1, _1, _2) },             // (4)
        {'/',[](double a, double b){ return a / b; }}};      // (5)

    std::cout << "3.5 + 4.5 = " << dispTable['+'](3.5, 4.5) << std::endl;
    std::cout << "3.5 - 4.5 = " << dispTable['-'](3.5, 4.5) << std::endl;
    std::cout << "3.5 * 4.5 = " << dispTable['*'](3.5, 4.5) << std::endl;
    std::cout << "3.5 / 4.5 = " << dispTable['/'](3.5, 4.5) << std::endl;

    std::cout << std::endl;

}

 

I use in this example a dispatch table (1) which maps characters to callables. A callable can be a function (1), a function object (2), a function object created by std::bind (3), or a lambda function. The key point of std::function is, that it accepts all different function types and erasure their types. std::function requires from its callables that its takes two double's and return a double: std::function<double(double, double)>.

To complete the example, here is the output.

callable

Before I write in the next post more about type erasure with templates, let me summarise the three techniques to implement type erasure.

TypeErasureCompare

You can implement type erasure with void-pointers, object-orientation, or templates. Only the implementation with templates is type safe and don't require a type hierarchy. The missing details to templates will follow.

What's next?

I assume, you want to know, how type erasure with templates is implemented? Of course, you have to wait for my next post.

 

 

 

Thanks a lot to my Patreon Supporters: Eric Pederson, Paul Baxter,  Meeting C++, Matt Braun, Avi Lachmish, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, and Mielo.

 

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 at educative

Modern C++ Concurrency in Practice: Get the most out of any machine

educative

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Run in browser
  • 78 code snippets
  • 55 illustrations

Add comment


Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 216

All 1091438

Currently are 195 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments