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.
Most of this lesson deals specifically with non-static members - that is, members that are invoked within the context of some object that is a member of that class.
#include <iostream>
class Character {
public:
int GetHealth() { return Health; }
int Health;
};
int main() {
Character P1{50};
Character P2{100};
// Calling GetHealth() in the context of P1
std::cout << P1.GetHealth() << '\n';
// Calling GetHealth() in the context of P2
std::cout << P2.GetHealth();
}
50
100
Within the body of a class function, that object is available through the this
pointer, and any other class functions or variables that the function directly references will also be invoked in that context:
class Character {
public:
int GetHealth() {
// Equivalent to this->Health
return Health;
}
bool isAlive() {
// Equivalent to this->GetHealth()
return GetHealth() <= 0;
}
int Health;
};
Most members work this way so when we define class members, they are non-static by default. However, C++ also allows us to define static members:
#include <iostream>
class Character {
public:
// Static member function
static float GetArmor() { return Armor; }
// Static data member declaration
static float Armor;
// Non-static data member
int Health{100};
};
// Static data member definition
float Character::Armor = 0.2;
Each object of a class get its own copy of non-static members which can be updated independently, but the value of non-static members is shared shared between all members of the class. That is, they point to the exact same memory location:
#include <iostream>
class Character {/*...*/};
int main() {
Character A;
Character B;
if (&A.Armor == &B.Armor) {
std::cout << "Same Memory Address\n";
}
if (&A.Health != &B.Health) {
std::cout << "Different Memory Address\n";
}
// Updating a static member updates it for
// all instances of the class
A.Armor = 0.5;
std::cout << B.Armor;
}
Same Memory Address
Different Memory Address
0.5
Because of this, static members do not need to be accessed in the context of any specific member. As a result, they will not have access to the this
pointer, and cannot directly use any other class members unless those members are also static
.
Conceptually, static members are very similar to free functions and variables, with the class acting like a namespace:
#include <iostream>
class Character {/*...*/};
int main() {
// Creating an object is unnecessary - we can
// directly access static members from the
// class using the :: operator
std::cout << Character::Armor << '\n';
std::cout << Character::GetArmor();
}
0.2
0.2
Pointers to static members work in the exact same way and have the same type as pointers to free functions and variables:
struct ExampleType{
static int Add(int x, int y) {
return x + y;
}
static int Value;
};
int ExampleType::Value = 42;
int Add(int x, int y) {
return x + y;
}
int main() {
int LocalVariable{50};
int* PointerA{&LocalVariable};
int* PointerB{&ExampleType::Value};
int (*PointerC)(int x, int y){
Add
};
int (*PointerD)(int x, int y){
&ExampleType::Add
};
}
We cover static members in more detail in a dedicated lesson later in the course.
Pointers to non-static member functions are a little more complex, as these functions must be called in the context of some instance of that class. Accordingly, their types are more complex, as they include the name of that class:
#include <string>
class Character {/*...*/};
int main() {
// Returns a std::string and accepts no args
std::string (Character::*Getter)(){
&Character::GetName};
// Returns void and accepts std::string arg
void (Character::*Setter)(const std::string&){
&Character::SetName};
}
We can introduce type aliases to make this syntax friendlier if desired:
#include <string>
class Character {/*...*/};
int main() {
using GetNamePtrType =
std::string (Character::*)();
using SetNamePtrType =
void (Character::*)(const std::string&);
GetNamePtrType Getter{&Character::GetName};
SetNamePtrType Setter{&Character::SetName};
}
The standard library includes a variety of helpers to make working with pointers to member functions easier. We’ll cover these later in this lesson.
Invoking a member function through a pointer to it is more complex than invocing free function pointers, functors, or lambdas. That is because, if the function we’re pointing at isn’t static, it needs to be invoked in the context of some member of that class.
As we’ve seen, when we know what we want to invoke, we provide the context object using the .
or ->
 operator:
#include <string>
class Character {/*...*/};
int main() {
// Call GetName() in the context of PlayerOne
Character PlayerOne;
PlayerOne.GetName();
// Call GetName() in the context of PlayerTwo
Character PlayerTwo;
PlayerTwo.GetName();
// Call SetName() in the context of PlayerTwo
Character* Ptr{&PlayerTwo};
Ptr->SetName("Anna");
}
When the thing we want to invoke is defined by a pointer to a member, C++ includes two operators, commonly called the pointer-to-member operators, to let us provide that context object.
.*
OperatorWith the .*
operator, we provide the context object as the left operand, and the member function pointer as the right. For example, if we have a member function pointer called Pointer
, and our context object is called Object
, our syntax would be Object.*Pointer
.
We then invoke what is returned by this operation using the ()
operator as normal. Note however that the ()
operator has higher precedence than the .*
operator. To ensure the .*
operation happens first, we need to introduce an extra set of parenthesis, giving (Object.*Pointer)()
.
Here’s an example:
#include <iostream>
class Character {/*...*/};
int main() {
using GetNamePtrType =
std::string (Character::*)();
GetNamePtrType GetName{&Character::GetName};
Character PlayerOne{"Anna"};
std::cout << (PlayerOne.*GetName)();
}
Anna
If our method requires arguments, we pass them within the ()
operator as normal:
#include <iostream>
class Character {/*...*/};
int main() {
using SetNamePtrType =
void (Character::*)(const std::string&);
SetNamePtrType SetName{&Character::SetName};
Character PlayerOne{"Anna"};
(PlayerOne.*SetName)("Roderick");
std::cout << PlayerOne.GetName();
}
Roderick
->*
OperatorThe .*
operator accepts a context object as the left operand, but what if we only have a pointer to the context object? We could dereference it in the normal way, using the *
 operator:
#include <iostream>
class Character {/*...*/};
int main() {
using GetNamePtrType =
std::string (Character::*)();
using SetNamePtrType =
void (Character::*)(const std::string&);
GetNamePtrType GetName{&Character::GetName};
SetNamePtrType SetName{&Character::SetName};
Character PlayerOne{"Anna"};
Character* PlayerPtr{&PlayerOne};
std::cout << (*PlayerPtr.*GetName)() << '\n';
(*PlayerPtr.*SetName)("Roderick");
std::cout << (*PlayerPtr.*GetName)();
}
Anna
Roderick
Alternatively, C++ provides the ->*
operator, allowing us to combine the *
and .*
operations into a single step. We provide the pointer to the context object as the left operand and the member function pointer as the right:
#include <iostream>
class Character {/*...*/};
int main() {
using GetNamePtrType =
std::string (Character::*)();
using SetNamePtrType =
void (Character::*)(const std::string&);
GetNamePtrType GetName{&Character::GetName};
SetNamePtrType SetName{&Character::SetName};
Character PlayerOne{"Anna"};
Character* PlayerPtr{&PlayerOne};
std::cout << (PlayerPtr->*GetName)() << '\n';
(PlayerPtr->*SetName)("Roderick");
std::cout << (PlayerPtr->*GetName)();
}
Anna
Roderick
std::invoke()
The std::invoke()
was added to the standard library in C++17, giving us an alternative way to call functions:
int Add(int x, int y) {
return x + y;
}
int main() {
Add(1, 2);
// Equivalently:
std::invoke(Add, 1, 2);
}
One of the benefits of std::invoke()
is that it provides a standard way to execute callables, regardless of the callable type. We’ve seen how invoking a lambda, for example, uses the basic ()
syntax, whilst a member function requires the ->*
or .*
 operator.
This is a problem as, when we’re writing a function that receives a callable as an argument, we typically want to support any type of callable:
#include <iostream>
class Player {
public:
std::string GetName() { return Name; }
std::string Name;
};
void Log(auto Getter, Player& Object) {
// Incompatible with member function pointers
std::cout << Getter(Object) << '\n';
// Incompatible with functors, lambdas, etc
std::cout << (Object.*Getter)() << '\n';
}
int main() {
Player PlayerOne{"Anna"};
Log(&Player::GetName, PlayerOne);
Log(
[](Player& P){ return P.Name; },
PlayerOne
);
}
error: term does not evaluate to a function taking 1 argument
error: '.*': not valid as right operand
We could work around that using compile-time techniques like assessing type traits, but std::invoke()
takes care of that for us.
To invoke a non-static member function using std::invoke
, we supply the context object as the second argument. std::invoke
can receive this by reference or pointer, including smart pointer:
#include <iostream>
class Player {
public:
std::string GetName() { return Name; }
std::string Name;
};
int main() {
Player PlayerOne{"Anna"};
auto Ptr{&Player::GetName};
std::cout << std::invoke(Ptr, PlayerOne) << '\n';
std::cout << std::invoke(Ptr, &PlayerOne);
}
Anna
Anna
If our member function requires arguments, we provide them after the context object:
#include <iostream>
class Player {
public:
void SetName(std::string NewName, bool Log) {
Name = NewName;
if (Log) {
std::cout << "Name is now " << Name;
}
}
std::string Name;
};
int main() {
Player PlayerOne;
auto Ptr{&Player::SetName};
std::invoke(Ptr, PlayerOne, "Anna", true);
}
Name is now Anna
In the following example, we’ve written Log()
using std::invoke()
, meaning it is now compatible with both lambdas and member function pointers:
#include <iostream>
class Player {
public:
std::string GetName() { return Name; }
std::string Name;
};
void Log(auto Getter, Player& Object) {
std::cout << std::invoke(Getter, Object) << '\n';
}
int main() {
Player PlayerOne{"Anna"};
Log(&Player::GetName, PlayerOne);
Log(
[](Player& P){ return P.Name; },
PlayerOne
);
}
Anna
Anna
std::is_member_pointer
Before C++17, or if we need to determine whether a type is a member pointer anyway, the standard library includes the std::is_member_pointer
type trait to help us:
#include <iostream>
#include <type_traits>
class Player {/*...*/};
template <typename T>
void Log(T Getter, Player& Object) {
if constexpr (std::is_member_pointer_v<T>) {
std::cout << "Member pointer: "
<< (Object.*Getter)() << '\n';
} else {
std::cout << "Not a member pointer: "
<< Getter(Object) << '\n';
}
}
int main() {
Player PlayerOne{"Anna"};
Log(&Player::GetName, PlayerOne);
Log([](Player& P){
return P.Name;
}, PlayerOne);
}
Member pointer: Anna
Not a member pointer: Anna
std::mem_fn()
The standard library’s <functional>
header includes std::mem_fn()
. This function accepts a member function pointer and returns a lightweight wrapper. This wrapper allows us to use that member pointer like a regular function, using the basic ()
 operator.
When invoking the functor returned by std::mem_fn()
, we provide the context object as the first argument:
#include <functional>
#include <iostream>
class Player {
public:
std::string GetName() { return Name; }
std::string Name;
};
int main() {
Player PlayerOne{"Anna"};
auto GetName{std::mem_fn(&Player::GetName)};
std::cout << GetName(PlayerOne);
}
Anna
If our member function requires additional arguments, we provide them after the context object:
#include <functional>
#include <iostream>
class Player {
public:
std::string GetName() { return Name; }
void SetName(const std::string& NewName) {
Name = NewName;
}
std::string Name;
};
int main() {
Player PlayerOne;
auto SetName{std::mem_fn(&Player::SetName)};
SetName(PlayerOne, "Anna");
std::cout << PlayerOne.GetName();
}
This utility is primarily useful if we need to provide a callable to an API that doesn’t directly support member function pointers, using a technique like std::invoke
, and we can’t update it.
We could handle this incompatibility by introducing an intermediate lambda, for example, but std::mem_fn()
takes care of that for us:
#include <functional>
#include <iostream>
class Player {
public:
std::string GetName() { return Name; }
std::string Name;
};
void Log(auto Getter, Player& Object) {
// Not compatible with member pointers
std::cout << Getter(Object) << '\n';
}
int main() {
Player PlayerOne{"Anna"};
// Intermediate lambda for compatibility:
Log([](const Player& P){
return P.Name;
}, PlayerOne);
// Alternative using std::mem_fn():
Log(std::mem_fn(&Player::GetName), PlayerOne);
}
Anna
Anna
In real-world applications, it’s quite rare for us to provide both a member function pointer and a context object to pass as the first argument.
Most real-world APIs will simply require a function they can call without needing to forward any consumer-provided arguments. To solve this, functional programming includes the notion of partial application, which allows us to provide only some of the required arguments.
This is sometimes referred to as binding, and the binding process returns a new callable object. This callable can then be invoked by passing only the remaining arguments, or no arguments at all if we bound everything that was required.
Below, we use std::bind()
to provide the context object to our Player::GetName()
function. The object returned by std::bind()
is then passed to the Log()
 function.
Player::GetName()
has no further parameters, so Log()
can invoke the bound object without needing to provide any arguments:
#include <functional> // For std::bind
#include <iostream>
class Player {
public:
std::string GetName() { return Name; }
std::string Name;
};
void Log(auto Getter) {
std::cout << Getter();
}
int main() {
Player PlayerOne{"Anna"};
Log(std::bind(&Player::GetName, &PlayerOne));
}
Anna
We cover function binding in detail in the next lesson.
Creating a pointer to a member function is controlled by member access specifiers like private
and protected
in the way we might expect.
In the following example, PrivateMethod
is private
, so our main
function cannot create a pointer to it:
#include <iostream>
class Player {
void PrivateMethod() {
std::cout << "Invoking Private Method";
}
};
int main() {
auto Ptr{&Player::PrivateMethod};
}
error: 'Player::PrivateMethod': cannot access private member declared in class 'Player'
However, member function pointers provide some additional flexibility here. We can provide access to private
or protected
functions through less restrictive methods:
class Player {
public:
static auto GetPtr() {
return &Player::PrivateMethod;
}
private:
void PrivateMethod() {
std::cout << "Invoking Private Method";
}
};
Once we have a pointer to a private
or protected
function, we can pass it around and invoke the function without any restrictions:
#include <iostream>
class Player {
public:
static auto GetPtr() {
return &Player::PrivateMethod;
}
private:
void PrivateMethod() {
std::cout << "Invoking Private Method";
}
};
int main() {
auto Ptr{Player::GetPtr()};
Player PlayerOne;
std::invoke(Ptr, &PlayerOne);
}
Invoking Private Method
virtual
MethodsMember function pointers interact with virtual
functions in the way we might expect. Below, our Combat
function accepts a reference to a Character
, and then calls the Attack()
 method.
We pass a Dragon
to this function but, because Character::Attack()
is not virtual
, our invocation is bound at compile time. Because Attacker
is a Character&
within the Combat()
function, Character::Attack()
will be used:
#include <iostream>
class Character {
public:
void Attack() {
std::cout << "Using Character Attack";
}
};
class Dragon : public Character {
public:
void Attack() {
std::cout << "Using Dragon Attack";
}
};
void Combat(auto Function, Character& Attacker) {
std::invoke(Function, Attacker);
}
int main() {
Dragon Monster;
Combat(&Character::Attack, Monster);
}
Using Character Attack
If we update Character::Attack()
to be virtual (and optionally update Dragon::Attack()
to be an override
), our invocation of Attacker.Attack()
is bound at run time.
Now, our Combat()
, function does additional work to determine the specific subtype the Attacker
has. It is a Dragon
in this example, so Dragon::Attack()
will be used:
#include <iostream>
class Character {
public:
virtual void Attack() {
std::cout << "Using Character Attack";
}
};
class Dragon : public Character {
public:
void Attack() override {
std::cout << "Using Dragon Attack";
}
};
// Unchanged
void Combat(auto Function, Character& Attacker) {
std::invoke(Function, Attacker);
}
// Unchanged
int main() {
Dragon Monster;
Combat(&Character::Attack, Monster);
}
Using Dragon Attack
const
As with most other types of pointer, the const
keyword can be used in two ways:
const
const
This creates four possible combinations. The first we’ve already seen - in previous examples, neither the pointer nor the function being pointed at was const
. Let’s see examples of the three other possibilities.
const
PointerA const
variable that stores a member function pointer works in the same way as any other const
variable. Once the variable is initialized, we can’t update it to point to a different function:
#include <iostream>
class Character {
public:
std::string GetName() const {
return Name;
}
void SetName(const std::string& NewName) {
Name = NewName;
}
std::string Name;
};
int main() {
using SetNamePtrType =
void (Character::*)(const std::string&);
Character PlayerOne;
// Creating a const pointer
const SetNamePtrType SetName{
&Character::SetName};
// This is fine
(PlayerOne.*SetName)("Anna");
// This is not
SetName = nullptr;
}
error: 'SetName': you cannot assign to a variable that is const
const
When a member function is const
, that const-ness will also be reflected in the type of any variable that stores a pointer to it:
#include <iostream>
class Character {
public:
std::string GetName() const {
return Name;
}
std::string Name;
};
int main() {
using GetNamePtrType =
std::string (Character::*)() const;
GetNamePtrType Func{&Character::GetName};
}
If an instance of our class is const
, any member function pointer we’re invoking on it must also be a pointer-to-const:
#include <iostream>
class Character {
public:
std::string GetName() const {
return Name;
}
void SetName(const std::string& NewName) {
Name = NewName;
}
std::string Name;
};
int main() {
using GetNamePtrType =
std::string (Character::*)() const;
using SetNamePtrType =
void (Character::*)(const std::string&);
GetNamePtrType GetName{&Character::GetName};
SetNamePtrType SetName{&Character::SetName};
// Creating a const Character
const Character PlayerOne;
// GetName is a pointer to const
// so this is fine
(PlayerOne.*GetName)();
// SetName is a pointer to non-const
// so this will generate a compilation error
(PlayerOne.*SetName)("Roderick");
// SetName itself is not const
// so this is fine:
SetName = nullptr;
}
error: 'argument': cannot convert from 'const Character' to 'Character &'
const
Pointer to const
Finally, we can use const
in both contexts, giving us a const
pointer to const
:
#include <iostream>
class Character {
public:
std::string GetName() const {
return Name;
}
void SetName(const std::string& NewName) {
Name = NewName;
}
std::string Name;
};
int main() {
using GetNamePtrType =
std::string (Character::*)() const;
const GetNamePtrType GetName{&Character::GetName};
SetName = nullptr;
}
error: 'GetName': you cannot assign to a variable that is const
We’re not restricted to creating pointers to member functions - we point to data members in the same way:
#include <iostream>
class Player {
public:
int GetScore() {
return Score;
}
int Score;
};
int main() {
Player John{50};
// Pointer to Function Member
int (Player::*FunctionPtr)(){&Player::GetScore};
std::cout << "Function Member: "
<<(John.*FunctionPtr)();
// Pointer to Data Member
int Player::*DataPtr{&Player::Score};
std::cout << "\nData Member using .* "
<< John.*DataPtr;
Player* JohnPtr{&John};
std::cout << "\nData Member using ->* "
<< JohnPtr->*DataPtr;
// Updating Data Member through Pointer:
std::cout << "\nUpdating Data Member using .* "
<< (John.*DataPtr += 50);
std::cout << "\nUpdating Data Member using ->* "
<< (JohnPtr->*DataPtr += 50);
}
Function Member: 50
Data Member using .* 50
Data Member using ->* 50
Updating Data Member using .* 100
Updating Data Member using ->* 150
An additional benefit of the std::invoke()
helper is that the first argument can be a pointer to a data member, giving us even more flexibility:
#include <iostream>
class Player {
public:
std::string Name;
};
void Log(auto Getter, Player& Object) {
std::cout << std::invoke(Getter, Object) << '\n';
}
int main() {
Player PlayerOne{"Anna"};
// We can use a lambda:
Log([](Player& P){
return P.Name;
}, PlayerOne);
// Or a simple pointer-to-member:
Log(&Player::Name, PlayerOne);
}
Anna
Anna
This is true of many other standard library utilities, including std::mem_fn()
and binding functions such as std::bind_front()
:
#include <iostream>
#include <functional>
class Player {
public:
int Score;
};
int main() {
Player John(50);
int Player::*Ptr{&Player::Score};
std::cout << "Using std::invoke: "
<< std::invoke(Ptr, &John) << '\n';
auto GetPlayerScore{std::mem_fn(Ptr)};
std::cout << "Using std::mem_fn: "
<< GetPlayerScore(&John) << '\n';
auto GetJohnScore{std::bind_front(Ptr, &John)};
std::cout << "Using std::bind_front: "
<< GetJohnScore();
}
Using std::invoke: 50
Using std::mem_fn: 50
Using std::bind_front: 50
Pointers to static data members are much simpler, behaving exactly like pointers to any other variable:
#include <iostream>
class Player {
public:
static int Health;
};
int Player::Health{100};
int main() {
int* PlayerHealth{&Player::Health};
std::cout << *PlayerHealth;
}
100
In this lesson, we've expanded our knowledge of function pointers to include member function pointers. Here are the key takeaways:
.*
and ->*
operators are used to invoke member functions through pointers.std::invoke
provides a modern, uniform way to call any callable object, including member function pointers.std::mem_fn()
wraps member function pointers for use in generic code and algorithms.std::function
can simplify the typing of complex member function pointers and provide a uniform interface for different types of callables.Learn how to create pointers to class functions and data members, and how to use them
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.