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.

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

Comments   

0 #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

Add comment


My Newest E-Books

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 153

All 456535

Currently are 172 guests and no members online