Previously, we’ve seen how function pointers, and first-class functions in general, give us a lot of flexibility in designing our programs.
In the following example, our Party
class has a for_each()
method which accepts a function argument. It then invokes that function for every Player
in the Party
:
#include <iostream>
class Player {/*...*/};
class Party {
public:
void for_each(auto Func) {
Func(PlayerOne);
Func(PlayerTwo);
Func(PlayerThree);
}
private:
Player PlayerOne{"Roderick"};
Player PlayerTwo{"Anna"};
Player PlayerThree{"Steve"};
};
This allows the external code to execute an action for each Player
. This design gives us a useful separation: the Party
class implements and controls the iteration process, whilst external code can specify the behavior they want to happen on each iteration.
Below, our program uses a function pointer to provide behavior defined within the Log()
 function:
#include <iostream>
class Player {/*...*/};
class Party {/*...*/};
void LogName(Player& P) {
std::cout << P.Name << '\n';
}
int main() {
Party MyParty;
MyParty.for_each(LogName);
}
Roderick
Anna
Steve
In this lesson, we’ll expand our techniques to include member function pointers - that is, pointers to functions that are part of a class
or struct
:
#include <iostream>
class Player {
public:
void LogName() {
std::cout << Name << '\n';
}
std::string Name;
};
class Party {/*...*/};
int main() {
Party MyParty;
// We want to use the `Player::LogName()` function
MyParty.for_each(/* ??? */);
}
This is more difficult than we might expect, so let’s break down all the complexities.
SDL_AddTimer()
to provide functions that are executed on time-based intervalsIn this lesson, we’ll introduce SDL’s timer mechanisms, which interact with function pointers to implement commonly required functionality. The capabilities this unlocks primarily fall into two categories:
To use SDL timers, we should pass the SDL_INIT_TIMER
flag to SDL_Init()
:
SDL_Init(SDL_INIT_TIMER);
We can combine multiple initialization flags using the bitwise |
 operator:
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
The biggest challenge we have when designing our program is managing how different components communicate with each other. If we manage this poorly, the complexity of our project will quickly get out of hand, making it extremely difficult to add new features or even understand what is going on.
However, if we manage this well, our project will stay understandable and easy to work with, even as we add more and more features.
Imagine we have a Player
object and a UIElement
class. The UIElement
needs to be displayed when our Player
is defeated. Let’s review some of the ways we can make this happen
std::bind()
, std::bind_front()
, std::bind_back()
and std::placeholders
.Earlier in the chapter, we introduced the concept of partial application. With partial application, we provide only some of a function’s required arguments - the remaining arguments are provided later, elsewhere in our code.
Being able to implement this approach gives us more flexibility in our designs, and is sometimes required. We introduced an example where we wanted to determine if a Player
object was a minimum level but, because of constraints in our design, we were not able to provide the Player
and MinimumLevel
at the same time.
We solved this using a functor. We provided the MinLevel
as a constructor argument, where it then got saved to a member variable. We then later provided the Player
object as an argument to the ()
 operator.
A simplified implementation of the full example we introduced in the Functors lesson is below:
#include <iostream>
struct Player {
int GetLevel() const { return 45; }
};
struct LevelChecker {
int MinLevel {50};
bool operator()(const Player& P) {
return P.GetLevel() >= MinLevel;
}
};
int main() {
Player PlayerOne;
LevelChecker IsLevel40{40};
LevelChecker IsLevel50{50};
if (IsLevel40(PlayerOne)) {
std::cout << "Player is level 40 or above";
}
if (!IsLevel50(PlayerOne)) {
std::cout << "\nBut not level 50 or above";
}
}
Player is level 40 or above
But not level 50 or above
In this lesson, we’ll cover a more direct and flexible way of implementing partial application, using std::bind
.
C++17 introduced fold expressions, which provide a shortened syntax to implement simple recursive algorithms that act on a parameter pack. A fold expression looks like this:
(Args + ...)
It has four components:
(
and )
Args
+
...
We’ll delve into the specific behavior of parameter packs throughout this lesson.
In the previous section, we introduced the std::pair
type, which allows us to create containers for storing two pieces of data, of any type.
We provide the two data types we will need to store within chevrons, <
and >
:
std::pair<int, float> MyPair;
We can then access the values stored in those slots using class variables called first
and second
:
#include <utility>
#include <iostream>
int main() {
std::pair<int, float> MyPair{42, 9.8f};
std::cout << "First " << MyPair.first
<< "\nSecond " << MyPair.second;
}
First: 42
Second: 9.8
std::pair
is an example of a template and, in this lesson, we’ll see how we can create our own templates to give similar flexibility.
So far, we've been working under the restrictions that functions must be declared before they are used. For example, we've been unable to create code like shown below:
int main() {
// Add is not defined yet
Add(1, 2);
}
int Add(int x, int y) {
return x + y;
}
Error: 'Add': identifier not found
Because the compiler reads our files top-to-bottom, when it reaches line 3, it encounters a call to a function that it’s not aware of.
The easy solution we've been using has been to move Add
above main
. However, we may not want to do this for stylistic reasons.
Worse, not all problems in this category can be solved by just moving functions around.
%
)In this short lesson, we'll explore how the modulus operator helps in obtaining the remainder after division. This has many applications and is particularly useful when we’re solving problems using loops.
We can imagine that the answer to a division question like might be , with left over. This is an example of modular arithmetic.
When performing division using modular arithmetic, we get two results:
FizzBuzz is a word game used to teach young school children about division. The students sit in a circle and take turns counting, one number each.
Any number that is divisible by 3 is replaced with the word "Fizz"; numbers divisible by 5 are replaced with "Buzz", and numbers divisible by 15 are replaced with "FizzBuzz".
The sequence looks like this:
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz,
11, Fizz, 13, 14, FizzBuzz, 16, 17...
This game, and variants of it, gained popularity within the programming community.
This is because implementing a program that can "solve" FizzBuzz requires a solid foundation in programming. It uses variables, operators, loops, conditional statements, and functions.
It is a useful milestone to ensure we understand all these basic principles. For this reason, candidates applying to programming jobs are sometimes asked to implement FizzBuzz in their interviews.
In the previous lesson, we introduced the concept of ticking and tick functions, which allow our game objects to update on every iteration of our application loop.
However, we should note that when we’re implementing logic in a Tick()
function, we don’t know how quickly our objects will tick. That is, we do not know how much time has passed since the previous invocation of that object’s Tick()
 function.
// Goblin.h
#pragma once
#include "GameObject.h"
class Goblin : public GameObject {
public:
int xPosition;
void Tick() override {
// This object moves by one pixel per
// invocation of Tick(), but how frequently
// is Tick() invoked?
xPosition += 1;
}
};
As we add more complexity to our tick functions or more ticking objects, our game will need to perform more work on each iteration of our application loop. As such, it will iterate less frequently, meaning Tick()
is invoked less frequently, meaning our object will move slower.
On the other hand, if we perform optimizations or our user has a more powerful computer, our loop can iterate faster, causing our objects to move faster.
To address this inconsistency, we typically want to define properties like movement speed in terms of real-world units of time, such as seconds or milliseconds. Let’s learn how to do this.