Chapter 1

Considering Functional Programming

IN THIS CHAPTER

check Understanding how functional programming works

check Defining how functional programming differs

check Implementing functional programming using lambda expressions

This minibook describes a different sort of C++ programming in the form of the functional programming paradigm. A paradigm is a framework that expresses a particular set of assumptions, relies on particular ways of thinking through problems, and uses particular methodologies to solve those problems. You’ll still use C++, but you use it in a manner that differs from the object-oriented programming (OOP) paradigms used in the previous minibook. Because many people are only now becoming aware of functional programming techniques, this chapter discusses how the functional and OOP paradigms differ.

The chapter also looks at some of the ways in which you change your programming style to use the functional programming paradigm. These style changes have some significant benefits when applied to certain kinds of development that rely heavily on math, perform various kinds of analysis, or work with technologies such as machine learning. You may not know it, but C++ is recommended as a language for both machine learning and deep learning in articles like the one at https://towardsdatascience.com/top-10-in-demand-programming-languages-to-learn-in-2020-4462eb7d8d3e. However, making it work in these environments requires use of functional programming techniques.

And finally in this chapter, you discover how to implement functional programming strategies using lambda expressions. This is one of the simplest ways to achieve what you want with a minimum of disruption to your standard programming practices if you’re heavily involved in OOP. Later chapters delve more deeply into lambda expressions. This chapter just helps you get your feet wet.

Remember You don’t have to type the source code for this chapter manually. In fact, using the downloadable source is a lot easier. You can find the source for this chapter in the \CPP_AIO4\BookIII\Chapter01 folder of the downloadable source. See the Introduction for details on how to find these source files.

Understanding How Functional Programming Differs

Functional programming has somewhat different goals and approaches than other paradigms use. Goals define what the functional programming paradigm is trying to do in forging the approaches used by languages that support it. However, the goals don’t specify a particular implementation; doing that is within the purview of the individual languages.

Remember The main difference between the functional programming paradigm and other paradigms is that functional programs use math functions rather than statements to express ideas. This difference means that rather than write a precise set of steps to solve a problem, you use math functions, and you don’t worry about how the language performs the task. In some respects, this makes languages that support the functional programming paradigm similar to applications such as MATLAB. Of course, with MATLAB, you get a user interface, which reduces the learning curve. However, you pay for the convenience of the user interface with a loss of power and flexibility, which functional languages do offer. Using this approach to defining a problem relies on the declarative programming style, which you see used with other paradigms and languages, such as Structured Query Language (SQL) for database management.

In contrast to other paradigms, the functional programming paradigm doesn’t maintain state. The use of state enables you to track values between function calls. Other paradigms use state to produce variant results based on environment, such as determining the number of existing objects and doing something different when the number of objects is zero. As a result, calling a functional program function always produces the same result given a particular set of inputs, thereby making functional programs more predictable than those that support state.

Because functional programs don’t maintain state, the data they work with is also immutable, which means that you can’t change it. To change a variable’s value, you must create a new variable. Again, this makes functional programs more predictable than other approaches and makes functional programs easier to run on multiple processors.

Technical stuff The capability to work on multiple processors is one area in which C++ excels. Most, possibly all, machines today have more than one core in their CPU, which allows for multiprocessing. Each core is essentially a single processor. Unlike many languages, C++ is uniquely positioned to make full use of the hardware, whether that hardware exists as a Graphics Processing Unit (GPU), Tensor Processing Unit (TPU), container, cloud, mobile device, or microcontroller. It’s this low-level affinity for the hardware and significant speed advantage that makes C++ the top choice for the Java Virtual Machine (JVM) and the Chrome V8 Engine.

Imperative programming, the kind of programming that most developers have done until now, is akin to an assembly line, where data moves through a series of steps in a specific order to produce a particular result. The process is fixed and rigid, and the person implementing the process must build a new assembly line every time an application requires a new result. Object-oriented programming (OOP) simply modularizes and hides the steps, but the underlying paradigm is the same. Even with modularization, OOP often doesn’t allow rearrangement of the object code in unanticipated ways because of the underlying interdependencies of the code.

Remember Functional programming gets rid of the interdependencies by replacing procedures with pure functions, which requires the use of immutable state. Consequently, the assembly line no longer exists; an application can manipulate data using the same methodologies used in pure math. The seeming restriction of immutable state provides the means to allow anyone who understands the math of a situation to also create an application to perform the math.

Using pure functions creates a flexible environment in which code order depends on the underlying math. That math models a real-world environment, and as our understanding of that environment changes and evolves, the math model and functional code can change with it — without the usual problems of brittleness that cause imperative code to fail. Modifying functional code is faster and less error prone than other programming paradigms because the person implementing the change must understand only the math and doesn’t need to know how the underlying code works. In addition, learning how to create functional code can be faster as long as the person understands the math model and its relationship to the real world.

Functional programming also embraces a number of unique coding approaches, such as the capability to pass a function to another function as input. This capability enables you to change application behavior in a predictable manner that isn’t possible using other programming paradigms.

Defining an Impure Language

Many developers have come to see the benefits of functional programming. However, they also don’t want to give up the benefits of their existing language, so they use a language that mixes functional features with one of the other programming paradigms (as described in the “Considering Other Programming Paradigms” sidebar). For example, you can find functional programming features in languages such as C++, C#, and Java. When working with an impure language, you need to exercise care because your code won’t work in a purely functional manner, and the features that you might think will work in one way actually work in another. For example, you can’t pass a function to another function in some languages. The following sections help you understand why C++ is an impure functional language.

Considering the requirements

The basis of functional programming is lambda calculus (https://brilliant.org/wiki/lambda-calculus/), which is actually a math abstraction. Every time you create and use a lambda function, you’re likely using functional programming techniques (in an impure way, at least). C++ supports lambda functions through the lambda expressions that later sections of this chapter explore.

In addition to using lambda functions, languages that implement the functional programming paradigm have some other features in common. Here is a quick overview of these features:

  • First-class and higher-order functions: First-class and higher-order functions both allow you to provide a function as an input, as you would when using a higher-order function in calculus.
  • Pure functions: A pure function has no side effects. When working with a pure function, you can
    • Remove the function if no other functions rely on its output
    • Obtain the same results every time you call the function with a given set of inputs
    • Reverse the order of calls to different functions without any change to application functionality
    • Process the function calls in parallel without any consequence
    • Evaluate the function calls in any order, assuming that the entire language doesn’t allow side effects
  • Recursion: Functional language implementations rely on recursion to implement looping. In general, recursion works differently in functional languages because no change in application state occurs.
  • Referential transparency: The value of a variable (a bit of a misnomer because you can’t change the value) never changes in a functional language implementation because functional languages lack an assignment operator.

Understanding the C++ functional limitations

C++ is actually an extension of C. The original name of C++ was C with classes. So, theoretically, pure C++ is an OOP language. However, with the introduction of the Standard Library (see Book 5, Chapter 6 as well as Book 7 for more on the Standard Library), it becomes possible to add functionality to the language and make it more generic. The use of Standard Library enables you to use the functional programming paradigm in C++. However, even with Standard Library, you can’t turn what started out as a procedural language and became an OOP language into a functional programming language. The best you can hope to achieve is a language that supports a number of paradigms — some of them in a general way.

What occurs in C++ for the most part is that you rely on the Standard Library to hide the nonfunctional programming components. For example, you can use constants in your C++ code to create an immutable environment. You use templates to create functions that don’t rely on variables and therefore have no state. Using constants with methods can also help eliminate the problems with side effects. You see all these principles demonstrated as the chapter progresses. However, unlike a pure language, such as Haskell, these conventions aren’t enforced in C++, and humans will routinely find ways around them when programming needs dictate.

Passing a function to a C++ function can also prove difficult unless you rely on the Standard Library. For example, you can use a transform to interact with a range of values by passing the transform a function. As part of the strategy of passing functions to other functions, you can rely on lambda expressions for simple needs. However, passing complex functions is possible as well. When working with complex functions, however, many developers encase them in a typedef to make the code easier to read.

To create a pure function in C++, you must eliminate both state and side effects, which can be quite difficult. The process becomes especially difficult when working with external data, such as a file or a data stream. Obviously, a function that works with external data won’t produce the same output every time you call it, but you can still reduce the problems of both state and side effects.

Even the use of recursion in place of the usual for or other looping mechanism can prove difficult in C++. In many cases, recursion relies on the use of mutable variables to track when the recursion should end. Careful use of various recursion strategies can make the use of mutable variables unnecessary, but doing so can be error prone and difficult (sometimes making the code hard to read).

Remember The takeaway from this section is that you can use C++ in a functional manner, but it requires additional work to do so. The benefits of this approach are that multiprocessing applications are easier to create, the code is more concise, and the code is often easier to understand as well. In some cases, you can’t use a functional programming style, especially when interacting with third-party libraries. However, if you work through coding issues using the Standard Library and some built-in C++ features, you can find yourself creating mostly functional code and obtaining the desired benefits from doing so.

Seeing Data as Immutable

Being able to change the content of a variable is problematic in C++. The memory location used by the variable is important. If the data in a particular memory location changes, the value of the variable pointing to that memory location changes as well. The concept of immutable data requires that specific memory locations remain untainted. To create immutable data in C++, you must use constant variables, as in

const double pi = 3.1415926;

The reason you need an immutable variable is that in a multiprocessing scenario, the value of the variable must be the same no matter which processor works with it. If x = 5 for one processor, it must equal 5 for all processors, and that value can never change. More important, the ability to change the value of a variable infers order, and functional programming techniques can’t rely on a specific order to accomplish their goals. Finally, immutable variables are reliable. You don’t have to worry about some bit of code, especially that from a hacker, modifying the values in your code because it seems like it might be a good idea. The following sections describe various forms of immutability in C++.

Working with immutable variables

The Immutable example, shown in Listing 1-1, demonstrates three techniques for creating immutable variables. In all three cases, you can rely on the variable’s value to remain consistent and also rely on the compiler to complain about any changes.

LISTING 1-1: Working with Constant Data

#include <iostream>

using namespace std;

struct Immutable{
int val{7};
};

int main() {
const int *test1 = new int(5);
*test1 = 10;

const int test2{6};
test2 = 11;

const Immutable test3;
test3.val = 12;

cout << *test1 << test2 << test3.val << endl;
return 0;
}

When you run this example, you see the following output in the Build Messages tab of the Code::Blocks compiler:

error: assignment of read-only location '* test1'
error: assignment of read-only variable 'test2'
error: assignment of member 'Immutable::val' in read-only
object

You can extend what you see here in other ways to make variables and their associated data immutable. Of course, now you have another problem — that of performing basic tasks, such as adding two numbers. To perform these tasks, you must begin using additional variables as containers like this:

const int sum = *test1 + test2;

Working with immutability in classes and structures

It’s essential to understand that immutability comes in several levels when working with C++ classes and structures. The Immutable2 example, shown in Listing 1-2, shows two levels of immutability. The first occurs in the Immutable structure, while the second occurs in main() when attempting to make a change.

LISTING 1-2: Creating Immutable Structure Members

#include <iostream>

using namespace std;

struct Immutable {
int val{1};

void SayHi(string Name) const {
Name = "Smith";
val = 2;
cout << Name << val << endl;
}

void ChangeVal() {
val = 3;
cout << val << endl;
}
};

int main() {
const Immutable Test;
Test.ChangeVal();
Test.SayHi("Sam");
return 0;
}

Figure 1-1 shows the error messages you receive when you attempt to compile this application. The first error occurs because the SayHi() method attempts to change val internally. Notice that ChangeVal() makes a similar change without error because it’s not a const method (as created by adding const after the method name and arguments to SayHi()). The second error occurs because the ChangeVal() call in main() attempts to change val through an external call.

Snapshot of seeing errors generated as the result of immutability in a structure.

FIGURE 1-1: Seeing errors generated as the result of immutability in a structure.

However, say that you want to allow internal changes to val, yet continue to deny external changes to enforce functional programming. Adding mutable to the val declaration: mutable int val{1}; allows internal changes. Consequently, a new build will generate only the ChangeVal() call error in main(). If you comment out this call, you can see that the example will build and generate the following output: Smith2. (The downloadable source provides these commented changes.)

Now the question is why it’s possible to change the Name value in SayHi(), if there aren’t supposed to be any changes. To make Name unchangeable, you must declare it as const, like this: void SayHi(const string Name) const. So, now you know how to add immutability at various levels within structures and classes (which work the same as structures, in this case).

Creating constant expressions

A constant expression, or constexpr, is a special kind of function that you can compute at compile time rather than runtime. You create the code, just as you would any code, but the compiler converts the code into an output before the application even runs, which means that this is one form of immutability that also lacks state. Listing 1-3 shows the ConstantExpression example that demonstrates how to create this kind of code. (This example won’t run with any version of C++ less than 11; the “Configuring Code::Blocks for smart pointers” sidebar in Book 1, Chapter 8 tells you how to perform this setup.)

LISTING 1-3: Creating Constant Expression Functions

#include <iostream>

using namespace std;

constexpr int factorial(int n) {
return n <= 1 ? 1 : (n * factorial(n - 1));
}

template<int n>
struct FactOut {
FactOut() {
cout << n << endl;
}
};

int main() {
// You can use a number if desired.
FactOut<15> Nothing1;

// Computed at compile time.
FactOut<factorial(4)> Nothing2;

// Computed at runtime.
cout << factorial(5) << endl;
return 0;
}

Tip This example adds some new features to the functional programming toolbox. For example, the factorial() function relies on recursion (where a function calls itself to perform a task) to perform its task. When n is something greater than 1, the function calls itself with a value of n – 1. Otherwise, it returns a value of 1 and the recursion unrolls itself by popping previous iterations from the stack.

Remember The FactOut structure uses a template parameter of template<int n> (the first place you see a template used in the book is the “Observers and the Standard C++ Library” section of Book 2 Chapter 4, but they’re explained in more detail in Book 5 Chapter 5). So, whatever you provide for n, it must evaluate to an int. Fortunately, the factorial() function does evaluate to an int, so you can use it as input to the template. Of course, the compiler wouldn’t know whether factorial() did provide an int output unless it computed it at compile time. This is one of the secrets of creating functional programs in C++: You need to think about templates. The FactOut structure contains nothing more than a constructor, and the constructor outputs the value provided as input to the template.

Here’s how all this works; main() begins by providing an int value of 15 to FactOut. The next line supplies factorial(4) as input to FactOut, but FactOut needs an int value, so the compiler computes the value during compile time. At runtime, FactOut still sees an int value, but this time it’s a computed int value. You can also use factorial() as a standard function, but in this case, the application computes the value at runtime.

Warning The variables Nothing1 and Nothing2 really do contain nothing. They satisfy the requirements of the compiler and nothing more. The compiler will raise an exception if you try to use them in your code. This isn’t to say that you can’t create other coded template forms that do offer some other level of functionality, but this form doesn’t allow that functionality. Here is another form of FactOut in which you can use the resulting variables:

template<int n>
struct FactOut {
int val;
FactOut() {
cout << n << endl;
val = n;
}
};

In this case, val contains the computed value of n. Consequently, you could use the variables you create like this: cout << Nothing1.val << endl;. However, now you’re introducing a mutable variable again. To avoid problems, you’d need to declare Nothing1 as const FactOut<15> Nothing1;.

Considering the Effects of State

Application state is a condition that occurs when the application performs tasks that modify global data. An application doesn’t have state when using functional programming. The lack of state has the positive effect of ensuring that any call to a function will produce the same results for a given input every time, regardless of when the application calls the function. However, the lack of state has a negative effect as well: The application now has no memory. When you think about state, think about the capability to remember what occurred in the past, which, in the case of an application, is stored as global data.

Avoiding state in any C++ application is nearly impossible. A problem area is any sort of file or stream data, which by nature changes. The FileLineCount example, shown in Listing 1-4, demonstrates two techniques for determining the number of lines in a file named Temp.txt. The first method, LineCount1(), relies on state to track the current number of lines and the current character. The second method, LineCount2(), doesn’t directly contain any sort of tracking; theoretically, it has no state.

LISTING 1-4: Avoiding the Use of State Directly

#include <iostream>
#include <fstream>
#include <algorithm>

using namespace std;

int LineCount1(string filename) {
int lineCount = 0;
char c = ' ';

ifstream thisFile(filename);

while (thisFile.get(c)) {
if (c == '\n')
lineCount++;
}

thisFile.close();

return lineCount;
}

int LineCount2(string filename) {
ifstream thisFile(filename);

return count(
istreambuf_iterator<char>(thisFile),
istreambuf_iterator<char>(), '\n');
}

int main() {
const string filename = "Temp.txt";

cout << LineCount1(filename) << endl;
cout << LineCount2(filename) << endl;
}

Remember When you call the two functions in main(), you get the same output. LineCount2() actually does appear to have no state. However, unlike the constant expression example in Listing 1-3, count() doesn’t perform the calculation during compile time. Doing so would be impossible because the number of times a newline in Temp.txt could change before the application runs. Consequently, the method shown in LineCount2() hides the use of state, but state information still resides at lower levels in the application. Unfortunately, this is about the best you’re going to get from C++ in the way of state elimination.

Technical stuff Note that istreambuf_iterator<char>() is an iterator, a kind of function that moves through a series of entries in some sort of data structure. In this case, you ask istreambuf_iterator<char>() to look through the characters in thisFile one character at a time. Every time count() sees a newline character, ’\n’, it adds one to the count. Normally, you must supply a beginning point and an ending point to count(). The second call to istreambuf_iterator<char>() says to continue checking characters until count() reaches the end of the file.

Eliminating Side Effects

The term declaration has a number of meanings in computer science, and different people use the term in different ways at different times. For example, in the context of a language such as C++, a declaration is a language construct that defines the properties associated with an identifier. You see declarations used for defining all sorts of language constructs, such as types and enumerations. However, that’s not how you use the term declaration in a functional programming sense. The following sections describe side effects in terms of declarations and functions in the functional programming sense of the term declaration.

Contrasting declarations and functions

When making a declaration in functional programming, you’re telling the underlying language to do something. For example, consider the following statement:

  1. Make me a cup of tea!

The statement tells simply what to do, not how to do it. The declaration leaves the execution of the task to the party receiving it and infers that the party knows how to complete the task without additional aid. Most important, a declaration enables someone to perform the required task in multiple ways without ever changing the declaration. However, when using a function (or method) named MakeMeTea (the identifier associated with the function), you might use the following sequence instead:

  1. Go to the kitchen.
  2. Get out the teapot.
  3. Add water to the teapot.
  4. Bring the pot to a boil.
  5. Get out a teacup.
  6. Place a teabag in the teacup.
  7. Pour hot water over the teabag and let steep for five minutes.
  8. Remove the teabag from the cup.
  9. Bring me the tea.

Remember A function details what to do, when to do it, and how to do it. Nothing is left to chance and no knowledge is assumed on the part of the recipient. The steps appear in a specific order, and performing a step out of order will cause problems. For example, imagine pouring the hot water over the teabag before placing the teabag in the cup. Functions are often error prone and inflexible, but they do allow for precise control over the execution of a task, and you use them far more often in C++ than you use declarations.

Declarations do suffer from another sort of inflexibility, however, in that they don’t allow for interpretation. When making a declarative statement (“Make me a cup of tea!”), you can be sure that the recipient will bring a cup of tea and not a cup of coffee instead. However, when creating a function, you can add conditions that rely on state to affect output. For example, you might add a step to the function that checks the time of day. If it’s evening, the recipient might return coffee instead of tea, knowing that the requestor always drinks coffee in the evening based on the steps in the function. A function therefore offers flexibility in its capability to interpret conditions based on state and provide an alternative output.

Declarations are quite strict with regard to input. The example declaration says that a cup of tea is needed, not a pot or a mug of tea. The MakeMeTea function, however, can adapt to allow variable inputs, which further changes its behavior. You can allow two inputs, one called size and the other beverage. The size input can default to cup and the beverage input can default to tea, but you can still change the procedure’s behavior by providing either or both inputs. The identifier, MakeMeTea, doesn’t indicate anything other than the procedure’s name. You can just as easily call it MyBeverageMaker.

Tip One of the hardest issues in moving from imperative languages to functional languages is the concept of declaration. For a given input, a functional language will produce the same output and won’t modify or use application state in any way. A declaration always serves a specific purpose and only that purpose.

The second hardest issue is the loss of control. The language, not the developer, decides how to perform tasks. Yet, you sometimes see functional code where the developer tries to write it as a function, usually producing a less-than-desirable result (when the code runs at all).

Associating functions with side effects

An essential difference between functions and declarations is that functions don’t return a value in the same manner as declarations do. The previous paragraphs present a function that seems to provide the same result as the associated declaration, but the two aren’t the same. The declaration “Make me a cup of tea!” has only one output: the cup of tea. The function has a side effect instead of a value. After making a cup of tea, the function indicates that the recipient of the request should take the cup of tea to the requestor. However, the function must successfully conclude for this event to occur. The function isn’t returning the tea; the recipient of the request is performing that task. Consequently, the function isn’t returning a value.

Side effects also occur in data. When you pass a variable to a function, the expectation in functional programming is that the variable’s data will remain untouched — immutable. A side effect occurs when the function modifies the variable data so that upon return from the function call, the variable changes in some manner.

Removing side effects

Because of the nature of the language, you have no magic bullet to use to kill side effects in C++. However, through disciplined coding, you can remove the side effects by observing some basic rules:

  • Never modify the incoming data.
  • Never rely on external data or modify any data outside the function.
  • Ensure that the function produces precisely the same result every time you provide a specific input.
  • Target the function so that it does one thing well, rather than multiple things adequately.
  • Make the function small.
  • Never repeat code or use boilerplate code.
  • Use the switch statement rather than if…then statements.
  • Use only immutable data.

The NoSideEffects example, shown in Listing 1-5, demonstrates these principles. No matter what you do outside the function, nothing changes the result given a particular input.

LISTING 1-5: Producing Code without Side Effects

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int AddIt(const vector<int> Input,
const int Start, const int End) {

int Accumulate = 0;

// Copy the full vector to a vector of the
// correct size.
vector<int> Process(End - Start);
copy(&Input[Start], &Input[End], Process.begin());

// Create a sum using a foreach loop.
for (int Element : Process)
Accumulate += Element;
return Accumulate;
}

int main() {
const vector<int> ThisVector = {12, 2, 4, 18, 7, 2};

cout << "Sum of All Elements: " <<
AddIt(ThisVector, 0, ThisVector.size()) << endl;
cout << "Sum of Elements 1 through 4: " <<
AddIt(ThisVector, 1, 5) << endl;
return 0;
}

Everything in this example is handled as a constant except the Accumulate and Process variables inside the AddIt() function. Consequently, there are no side effects. Any changes occur only within AddIt(), and AddIt() will always produce the same output for a given input.

To process just the ThisVector elements that are needed for the summation, AddIt() creates a copy of the Input vector using the copy() function. (Don’t worry about the use of a vector right now; you see them explained in detail in Book 5, Chapter 6.) Notice that by using Standard Library functionality, you can avoid the appearance of state for the most part in this function. Even the foreach loop (implemented as a special case of the for loop):

for (int Element : Process)
Accumulate += Element;

avoids the usual state information needed to power the loop. Theoretically, you could create a recursive solution to this problem that wouldn’t use state at all. Here’s the output from the example:

Sum of All Elements: 45
Sum of Elements 1 through 4: 31

Technical stuff You might wonder why this example doesn’t use an array instead of a vector. The problem with std::array is that you must provide an array size, such as array<int, 6> ThisVector = {12, 2, 4, 18, 7, 2}; so that the array size is known at compile time. However, you don’t know the size of the Process array at compile time because the call to AddIt() provides for a variable starting and ending point. One way around this issue would be to use a constexpr setup, as shown earlier in Listing 1-3.

Creating a declarative C++ example

Even though Listing 1-5 goes a long way toward making the C++ code easy to understand and considerably more bulletproof than you might otherwise expect, you can go one step further in that effort without resorting to anything odd in the way of coding. The Declarative example shown in Listing 1-6 relies on the Standard Library even further to eliminate the need for a separate function.

LISTING 1-6: Using Declarative Programming Techniques

#include <iostream>
#include <array>
#include <numeric>

using namespace std;

int main() {
array<int, 6> ThisArray = {12, 2, 4, 18, 7, 2};
cout << "Sum of All Elements: " <<
accumulate(ThisArray.begin(), ThisArray.end(), 0)
<< endl;
cout << "Sum of Elements 1 through 4: " <<
accumulate(&ThisArray[1], &ThisArray[5], 0) << endl;
return 0;
}

This example uses the std::accumulate() function to perform the required work. There are a number of interesting functions of this sort in the numeric header, which you can see at https://en.cppreference.com/w/cpp/header/numeric. Notice that the majority of these functions require C++ 11, C++ 17, or even C++ 20 to use, so they’re more appropriate for new development. The output from this example is precisely the same as the output from Listing 1-5; only the technique changes.

Remember One of the more interesting aspects of this example is that you work with an array and allow the underlying code to handle the how of creating the sum. This code doesn’t worry about any sort of procedure at all; it simply tells the Standard Library to accumulate (sum) the values together.

Notice also the two methods used to provide the starting and ending points for the calculation. What you need is an address. The first call uses the begin() and end() functions to supply the address, and the second call relies on the address provided by the [] operator.

Understanding the Role of auto

Starting with C++ 11, you can use the auto keyword in place of a specific type declaration. The use of the auto keyword comes in handy when you don’t know what data type to expect in advance. When you run the application, the runtime deduces the type of the variable so that you can work with it correctly. Using this technique helps you create flexible code, even if it does reduce the clarity of your code a little. The Auto example, shown in Listing 1-7, shows how to use this keyword to perform various tasks.

LISTING 1-7: Using the auto Keyword

#include <iostream>
#include <typeinfo>

using namespace std;


void DisplayIt(auto Value) {
cout << Value << " is of the " <<
typeid(Value).name() << " type." << endl;
}

int main() {
auto Hello1 = "Hello There!";
string Hello2 = "Hello Again!";
auto Number1 = 1234;
int Number2 = 5678;
auto Float1 = 12.34;
float Float2 = 56.78;
auto Boolean1 = true;
bool Boolean2 = false;

DisplayIt(Hello1);
DisplayIt(Hello2);
DisplayIt(Number1);
DisplayIt(Number2);
DisplayIt(Float1);
DisplayIt(Float2);
DisplayIt(Boolean1);
DisplayIt(Boolean2);

return 0;
}

The code begins by creating a number of variables — with half using standard declarations and half using the auto keyword. It then calls DisplayIt() to display the variable value and type. By using the auto keyword, DisplayIt() can accept all these inputs and interact with them appropriately.

Tip Even though this code works, it has a problem. The typeid() function often returns a mangled result depending on the compiler you use. Here’s an example:

Hello There! is of the PKc type.
Hello Again! is of the NSt7__cxx1112basic_stringIcSt11char
_traitsIcESaIcEEE type.
1234 is of the i type.
5678 is of the i type.
12.34 is of the d type.
56.78 is of the f type.
1 is of the b type.
0 is of the b type.

Although you can probably figure the i, d, f, and b entries out, the PKc entry is a mystery, and forget trying to determine the type of the next line that begins with NSt7. You’ll likely want the output in human-readable form, which requires a few additional steps, starting with the addition of two new #include entries.

#include <memory>
#include <cxxabi.h>

The DemangleIt() function takes the mangled input from DisplayIt() and forms it into a human-readable string, as shown here:

string DemangleIt(const char* Mangled) {
int Status;
unique_ptr<char[], void(*)(void*)> Result(
abi::__cxa_demangle(Mangled, 0, 0, &Status), free);
return Result.get() ? string(Result.get()) : "Error";
}

The call to abi::__cxa_demangle() performs the actual result. What you receive is a unique_ptr, Result, that contains a pointer to the human-readable form of the type. If the abi::__cxa_demangle() call isn’t successful, Result will contain a null pointer, and you can return a result of "Error" in place of the actual type string. To make this code functional, you need to modify DisplayIt(), as shown here:

void DisplayIt(auto Value) {
cout << Value << " is of the " <<
DemangleIt(typeid(Value).name()) << " type." << endl;
}

Now when you run the example, you see the output in human-readable form, which makes working with it a lot easier.

Hello There! is of the char const* type.
Hello Again! is of the std::__cxx11::basic_string<char,
std::char_traits<char>, std::allocator<char> > type.
1234 is of the int type.
5678 is of the int type.
12.34 is of the double type.
56.78 is of the float type.
1 is of the bool type.
0 is of the bool type.

Warning At this point, you should notice something about using auto: You may not always get the expected type. In this case, the string declared using auto is of a different type than the string declared using string. The deduction process often relies on default types as well. For example, if you mean to use a float, but declare the variable as auto, the result will be a double instead because that’s the default type.

Passing Functions to Functions

Sometimes you need to apply a process to a group of numbers, or you need to apply more than one process to a single number. In fact, sometimes you need to do both. When you encounter situations like this, the easiest method of dealing with them is to pass a function, the process you want to perform, to another function that handles the situation. In the sections that follow, you begin by seeing a simple example of performing this task on a single number using multiple processes. You also see how to apply a single process to a group of numbers in a technique called a transform, because you’re transforming one series of numbers into another series of numbers.

Seeing a simple example of function input

At times, a single number represents a base value, but you must manipulate it in various ways to achieve a result. For example, you might need to find the correct process to use to optimize a particular set of values using a base value as a starting point. The FunctionFunction example, shown in Listing 1-8, demonstrates how to use this technique.

LISTING 1-8: Passing a Function to a Function

#include <iostream>
#include <vector>

using namespace std;

int AddSome(int Value) {
return Value + 10;
}

int DelSome(int Value) {
return Value - 10;
}

int MulSome(int Value) {
return Value * 10;
}

int DivSome(int Value) {
return Value / 10;
}

typedef int(*FuncPtr)(int);

void ModIt(int Value, vector<FuncPtr> FuncArray) {
int NumFunc = FuncArray.size();
cout << "Processing " << NumFunc << " functions."
<< endl;

for(int i = 0; i < NumFunc; i++)
cout << FuncArray[i](Value) << endl;
}

int main() {
vector<FuncPtr> FuncArray =
{*AddSome, *DelSome, *MulSome, *DivSome};
ModIt(10, FuncArray);
return 0;
}

In most cases when you use this technique, you create an array or vector of function pointers. Using a vector is more flexible because you don’t have to predetermine the number of functions to pass — it can be any number up to the maximum size of the vector. To make this technique work, however, you must begin by creating a typedef that defines the form of each function pointer entry consisting of the

  • Return value, which is int
  • Pointer to the function in parentheses, which is (*FuncPtr)
  • Input parameters in parentheses, which is (int)

Tip The typedef, the creation of a new name for a type of object, appears in quite a few places in the book. For example, in Book 4, Chapter 1 you see it used to work with a vector to process strings. Book 5, Chapter 1 demonstrates how to use a typedef with a multidimensional array. In fact, Book 5 is the place to go if you want to gain a full appreciation of all the uses for a typedef.

You define the vector as vector<FuncPtr> with a vector name, such as FuncArray. Creating the vector then becomes a matter of providing pointers to the four functions used for testing in this case: AddSome(), DelSome(), MulSome(), and DivSome(). These four functions don’t do much, but they do help in testing.

The code calls ModIt() with the value you want to work with, which is 10, and the vector of function pointers, FuncArray. Inside ModIt(), the code calls each of the functions in turn with the supplied value and outputs the result onscreen. Here is the output from this example:

Processing 4 functions.
20
0
100
1

Using transforms

A transform allows you to process a series of values using a single function. Combining a series of transforms enables you to process a series of values using a series of functions in a particular order. You see transforms used in all sorts of ways, including to condition data and process video. The Transform example, shown in Listing 1-9, gives you an overview of how this technique works using the C++ range functionality.

LISTING 1-9: Using a Transform on a Series of Data Points

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct EvenPair {
int Value;
bool Even;
};

EvenPair IsEven(int Value){
if (Value % 2 == 0)
return EvenPair{Value, true};

return EvenPair{Value, false};
}

int main(){
vector<int> Values{1, 2, 3, 4};
vector<EvenPair> Evens(Values.size());

transform(Values.begin(), Values.end(),
Evens.begin(), IsEven);

for(auto isEven : Evens)
if (isEven.Even)
cout << isEven.Value << " is even." << endl;
else
cout << isEven.Value << " is odd." << endl;

return 0;
}

This example uses the EvenPair structure to hold two variables that contain the original value you want to check and show whether that value is even. In main(), you begin by creating two vectors: one input, Values, and one output, Evens. The Evens vector will contain a list of the original values and a Boolean showing whether each value is even.

Remember The call to Transform() takes pointers to the beginning and ending of Values, the beginning of Evens, and the name of a function to use for the transformation. In this case, IsEven() receives an individual Value, determines whether it’s even using the mod operator Value % 2, and then outputs a Value and Even pair.

After the transformation completes, a foreach loop checks each value in Evens and outputs an appropriate string. Here are the results:

1 is odd.
2 is even.
3 is odd.
4 is even.

Using Lambda Expressions for Implementation

A lambda expression is an unnamed function that you can use in place of a regular function reference. Using a lambda expression can make your code more readable by placing the function inline. Chapters 2 and 3 of this minibook cover lambda expressions in detail, but the Lambda example, shown in Listing 1-10, shows an alternative way to create the code displayed in Listing 1-9 in a shorter way.

LISTING 1-10: Performing a Transform Using a Lambda Expression

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct EvenPair {
int Value;
bool Even;
};

int main(){
vector<int> Values{1, 2, 3, 4};
vector<EvenPair> Evens(Values.size());

transform(Values.begin(), Values.end(),
Evens.begin(), [](int Value) {
return (Value % 2 == 0)
? EvenPair{Value, true}
: EvenPair{Value, false};});

for(auto isEven : Evens)
if (isEven.Even)
cout << isEven.Value << " is even." << endl;
else
cout << isEven.Value << " is odd." << endl;

return 0;
}

The basic idea of this example is the same as the example in the “Using transforms” section, earlier in this chapter, except that it uses a lambda expression in place of the call to IsEven(). The lambda expression begins with a capture clause, [], which defines how to capture any required external variables. An empty capture clause says that the lambda expression can work only with variables that are local to it, which is Value in this case.

As with IsEven(), the lambda expression requires an int input, Value. The compiler deduces the output type based on the lambda expression code. However, you can specify the output type directly when needed using -> output_type. In this case, you’d use [](int Value) -> EvenPair in place of the code shown.

The output is one of two values, as determined by a ternary operator. When (Value % 2 == 0) is true, the output is EvenPair{Value, true}; otherwise, the output is EvenPair{Value, false}. The point is that this version is shorter than the version in Listing 1-9, so lambda expressions can make your code shorter and easier to understand when the function you want to use is small.