Functional in C++11 and C++14: Dispatch Table and Generic Lambdas

Contents[Show]

My favourite example the dispatch table shows how nice the features in modern C++ work together. A dispatch table is a table of pointers to functions. In my case, it is a table of handles to polymorphic function wrappers.

 

But at first, what do I mean by modern C++. I use in the dispatch table features from C++11. I added in this post C++14 to the timeline. Why? You will see later.

timeline.FunktionalInCpp11Cpp14Eng

Dispatch table

Thanks to Arne Mertz, I used the C++11 features uniform initialization in combination with an initializer list. That further improved the following example.

The example shows a simple dispatch table that maps characters to function objects.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// dispatchTable.cpp

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

int main(){

  std::cout << std::endl;

  // dispatch table
  std::map< const char , std::function<double(double,double)> > dispTable{
    {'+',[](double a, double b){ return a + b;} },
    {'-',[](double a, double b){ return a - b;} },
    {'*',[](double a, double b){ return a * b;} },
    {'/',[](double a, double b){ return a / b;} } };

  // do the math
  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;

  // add a new operation
  dispTable['^']=  [](double a, double b){ return std::pow(a,b);};
  std::cout << "3.5^4.5= " << dispTable['^'](3.5,4.5) << std::endl;

  std::cout << std::endl;

};

 

How does the magic work? The dispatch table is my case a std::map that contains pairs of const char and std::function<double(double,double). Of course, you can use a std::unordered_map instead of a std::map. std::function is a so called polymorphic function wrapper. Thanks to std::function, it can take all that behaves like a function. This can be a function, a function object, or a lambda-function (line 14 -17). The only requirements of std::function<double(double,double)> is that its entities needs two double arguments and return a double argument. This requirement is fulfilled by the lambda-functions.

I use the function object in the lines 20 - 23. Therefore, the call of dispTable['+'] in line 20 returns that function object which was initialized by the lambda-function [](double a, double b){ return a + b; } (line 14). To execute the function object, two arguments are needed. I use them in the expression dispTable['+'](3.5, 4.5).

A std::map is a dynamic data structure. Therefore, I can add and use the '^' operation (line 27) at runtime. Here is the calculation.

dispatchTable

Still, a short explanation is missing. Why is this my favourite example in C++?

Like in Python

I often give Python seminars. One of my favourite examples to motivate the easy usage of Python is a dispatch table. That is by the way the reason why Python needs no case statement.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# dispatchTable.py

dispTable={
  "+": (lambda x, y: x+y),
  "-": (lambda x, y: x-y),  
  "*": (lambda x, y: x*y),
  "/": (lambda x, y: x/y)
}

print

print "3.5+4.5= ", dispTable['+'](3.5, 4.5)
print "3.5-4.5= ", dispTable['-'](3.5, 4.5)
print "3.5*4.5= ", dispTable['*'](3.5, 4.5)
print "3.5/4.5= ", dispTable['/'](3.5, 4.5)

dispTable['^']= lambda x, y: pow(x,y)
print "3.5^4.5= ", dispTable['^'](3.5, 4.5)

print

 

The implementation is based on the functional features of Python. Thanks to std::map, std::function, and lambda-functions, I can use now the same example in C++11 to emphasis the expressive power of C++. A fact I would not have dreamed 10 years ago.

dispatchTablePython 

Generic lambda-functions

I almost forgot it. Lambda-functions becomes more powerful with C++14. Lambda-function can automatically deduce the types of its arguments. The feature is based on automatic type deduction with auto. Of course, lambda-functions and automatic type deduction are characteristics of functional programming.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// generalizedLambda.cpp

#include <iostream>
#include <string>
#include <typeinfo>

int main(){
    
  std::cout << std::endl;
  
  auto myAdd= [](auto fir, auto sec){ return fir+sec; };
  
  std::cout << "myAdd(1, 10)= " << myAdd(1, 10) << std::endl;
  std::cout << "myAdd(1, 10.0)= " << myAdd(1, 10.0) << std::endl;
  std::cout << "myAdd(std::string(1),std::string(10.0)= " 
            <<  myAdd(std::string("1"),std::string("10")) << std::endl;
  std::cout << "myAdd(true, 10.0)= " << myAdd(true, 10.0) << std::endl;
  
  std::cout << std::endl;
  
  std::cout << "typeid(myAdd(1, 10)).name()= " << typeid(myAdd(1, 10)).name() << std::endl;
  std::cout << "typeid(myAdd(1, 10.0)).name()= " << typeid(myAdd(1, 10.0)).name() << std::endl;
  std::cout << "typeid(myAdd(std::string(1), std::string(10))).name()= " 
            << typeid(myAdd(std::string("1"), std::string("10"))).name() << std::endl;
  std::cout << "typeid(myAdd(true, 10.0)).name()= " << typeid(myAdd(true, 10.0)).name() << std::endl;
    
  std::cout << std::endl;

}

 

In line 11, I use the generic lambda-function. This function can be invoked with arbitrary types for its arguments fir and second and deduces in addition automatically its return type. I order to use the lambda-function, I given the lambda-function the name myAdd. Line 13 - 17 shows the application of the lambda-function. Of course, I'm interested which type the compiler derives for the return type. For that, I use the typeid operator in the lines 21 -25. This operators needs the header <typeinfo>.

The typeid operator is not so reliable. It returns a C string, which depend on the implementation. You have not the guarantee that the C string is different for different types nor that the C string is the same for each invocation of the program. But for our use case, the typeid operator is reliable enough.

My desktop PC is broken, therefore I execute the program at cppreference.com.

generalizedLambdaFunctions

The output shows the different return types. The C string i and d stands for the types int and double. The type of the C++ strings is not so good readable. But you can see, that std::string is an alias for std::basic_string.

What's next?

I the next post I will write about the near and distant functional future of C++. With C++17 and C++20 the functional aspect of C++ become more powerful.

 

 

 

 

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

Tags: lambdas

Comments   

+1 #1 Thales 2017-01-20 20:42
Do you know if is possible to implement a generic dispatch table in c++? I think that the problem would be to store lambdas with different signatures in the same map.
Quote
0 #2 Rainer Grimm 2017-01-21 08:29
Quoting Thales:
Do you know if is possible to implement a generic dispatch table in c++? I think that the problem would be to store lambdas with different signatures in the same map.

I think, you have to use a std::tuple or a std::variant (C++17). To be honest, I see no use-case for generalized lambdas. Maybe you have one.
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 3924

Yesterday 7709

Week 28020

Month 186451

All 5055765

Currently are 187 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments