First-Class Functions

Contents[Show]

One of the characteristics of functional programming is first-class functions. First-class functions behave like data and are heavily used in the Standard Template Library.

 

First-class functions

CharakteristikenFunktionaleProgrammierungFirstClassFunctionsEng

Do first-class functions behave like data? What does that mean?

First-class functions can be

  • an argument of a function.

    std::accumulate(vec.begin(), vec.end(), 1, []{ int a, int b}{ return a * b; })

  • returned from a function.
       std::function<int(int, int)> makeAdd(){
         return [](int a, int b){ return a + b; };
       }
       std::function<int(int, int)> myAdd= makeAdd();
       myAdd(2000, 11);  // 2011
  • stored in a variable.

One short remark to the second point. The function makeAdd returns the lambda function [](int a, int b){ return a + b; }. This function needs two int arguments and returns an int value: std::function<int(int,int)>. You can bind the return value of makeAdd to the generic function wrapper myAdd and execute it.

Which power lies in first-class functions is shown in my post Functional in C++11 and C++14: Dispatch Table and Generic Lambdas. Have a look at the dispatch table in C++.

Function pointers are the first-class functions of the poor man. Even C has function pointers. C++ goes a few steps further. In particular, you can connect the evolution of the function concept with the evolution of C++.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Stay informed about my mentoring programs.

 

 

Subscribe via E-Mail.

The evolution of the function concept

Simplified, the evolution consists of four steps.

FunctionEvolutionEng

C knows functions. C++ added function objects. These are objects, which behave like functions because their call operator is overloaded. C++11 added lambda-functions; C++14 made them generic. Each step made functions in C++ even more powerful.

  • Functions => Function objects: Function object can have in contrary to functions a state. Therefore, they have a kind of memory and you can configure them.
  • Function objects => Lambda functions: Lambda functions are usually implemented just in place of their usage. That improves the readability of the code, reduces your typing to its bare minimum, and gives the optimizer maximum insight into your code. Therefore, the optimizer has more opportunities to do its job.
  • Lambda functions => Generic lambda functions: Generic lambda functions are quite similar to function templates but are a lot easier to implement. They can be widely used because as closures they capture their environment and you can parametrize their type. 

Admittedly, that was a lot of theory. Therefore, the example is following right now. Here are the four steps of the function concepts in C++.

 

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// evolutionOfFunctions.cpp

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

std::string addMe(std::string fir, std::string sec){
  return fir + " " + sec;
};

struct AddMe{
  
  AddMe()= default;
  AddMe(std::string gl): glue(gl) {}
  
  std::string operator()(std::string fir, std::string sec) const {
    return fir + glue + sec;
  }
  
  std::string glue= " ";
  
};

int main(){
  
  std::vector<std::string> myStrings={"The", "evolution", "of", "the", "function", "concept", "in", "C++."};
  
  std::string fromFunc= std::accumulate(myStrings.begin(), myStrings.end(), std::string{}, addMe);
  
  std::cout << fromFunc << std::endl;
  
  std::cout << std::endl;
  
  std::string fromFuncObj= std::accumulate(myStrings.begin(), myStrings.end(), std::string{}, AddMe());
  
  std::cout << fromFuncObj << std::endl;
  
  std::string fromFuncObj2= std::accumulate(myStrings.begin(), myStrings.end(), std::string{}, AddMe(":"));
  
  std::cout << fromFuncObj2 << std::endl;
  
  std::cout << std::endl;
  
  std::string fromLambdaFunc= std::accumulate(myStrings.begin(), myStrings.end(), std::string{}, [](std::string fir, std::string sec){ return fir + " " + sec; });
  
  std::cout << fromLambdaFunc << std::endl;
  
  std::string glue=":";
  
  auto lambdaFunc= [glue](std::string fir, std::string sec){ return fir + glue + sec; };
  
  std::string fromLambdaFunc2=  std::accumulate(myStrings.begin(), myStrings.end(), std::string{}, lambdaFunc);
  
  std::cout << fromLambdaFunc2 << std::endl;
  
  std::cout << std::endl;
  
  std::string fromLambdaFuncGeneric= std::accumulate(myStrings.begin(), myStrings.end(), std::string{}, [glue](auto fir, auto sec){ return fir + glue + sec; });
  
  std::cout << fromLambdaFuncGeneric << std::endl;
  
  auto lambdaFuncGeneric= [](auto fir, auto sec){ return fir + sec; };
  
  auto fromLambdaFuncGeneric1= std::accumulate(myStrings.begin(), myStrings.end(), std::string{}, lambdaFuncGeneric);
  
  std::cout << fromLambdaFuncGeneric1 << std::endl;
  
  std::vector<int> myInts={1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  
  auto fromLambdaFuncGeneric2= std::accumulate(myInts.begin(), myInts.end(), int{}, lambdaFuncGeneric);
  
  std::cout << fromLambdaFuncGeneric2 << std::endl;
  
}

 

The program begins with the function addMe (line 8 - 10), the function object AddMe (line 12 - 23), the lambda function (line 45 and 51), and the generic lambda function in line 59, 63, and 64. The purpose of the program is to concatenate in different ways the strings of the vector myString (line 27) with the help of the algorithm std::accumulate. Therefore, it is quite comfortable that std::accumulate is a higher-order function (wait for my next post) and can accept first-class functions. I use a function pointer, a function object and a (generic) lambda function as the first-class function.

I apply in line 29 the function pointer addMe. The function object AddMe and AddMe(:) (line 35 and 39) are more comfortable to use because I can parametrize the glue between the strings. The lambda functions offer the same comfort (line 45 and 51). In particular, the lambda function lambdaFunc uses a copy of the variable glue (line 49). Therefore, the lambda function is a closure. The generic lambda function [glue](auto fir, auto sec){ return fir + glue + sec; }) (line 59) is the most interesting one. As closure, they also use the variable glue. I apply the generic lambda function lambdaFuncGeneric to a std::vector<std::string> (line 27) and a std::vector<int> (line 69). In case of std::vector<int> the result is 55.

In the end, the output of the program.

 evolutionOfFunctions

The evolution will not end with C++14. In C++20 we get high probability coroutines, which are a generalisation of functions. Coroutines can be suspended and resumed. I will write a post about it. So, stay tuned.

But, that can be done easier

For didactical reasons, I used std::accumulate to add pairs of elements together. That can be done a lot easier - even for generic lambda functions - because std::accumulate has a simple version, which needs no callable. The simple version just adds their elements together starting with the first element. Therefore, I can write line 65 a lot simpler: auto fromLambdaFuncGeneric1= std::accumulate(myStrings.begin(), myStrings.end(), std::string{}). The same holds for fromLambdaFuncGeneric2.

What's next?

I already mentioned it in the post. The classical counterpart to first-class functions is higher-order functions because they get first-class functions. The next post will be about higher-order functions. The next property of functional programming.

 

 

 

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, and Ann Shatoff.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

Seminars

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

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

  • C++ - The Core Language
  • C++ - The Standard Library
  • C++ - Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

 

Comments   

0 #1 pmalhaire 2018-04-14 20:27
Those posts about "functionnal cpp" are amazing.

It seems that there is something wrong (or I don't get it) in this line :
std::accumulate(vec.begin(), vec.end(), []{ int a, int b}{ return a+b; })
I'd expect this :
std::accumulate(vec.begin(), vec.end(), 0,[]( int a, int b){ return a+b; })
Quote
0 #2 Rainer Grimm 2018-04-15 06:30
Quoting pmalhaire:
Those posts about "functionnal cpp" are amazing.

It seems that there is something wrong (or I don't get it) in this line :
std::accumulate(vec.begin(), vec.end(), []{ int a, int b}{ return a+b; })
I'd expect this :
std::accumulate(vec.begin(), vec.end(), 0,[]( int a, int b){ return a+b; })

Thanks you are right. I also changed + to * because + is the default for std::accumulate.
Quote

Mentoring

Stay Informed about my Mentoring

 

English 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

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 3835

Yesterday 5317

Week 3835

Month 148006

All 11629160

Currently are 244 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments