Understanding std::mem_fn in Modern C++
In the C++ programming language, member function pointers have a
distinct and cumbersome syntax compared to standard callable entities
like free functions or lambdas. The primary purpose of
std::mem_fn is to wrap pointers to members—both member
functions and member data—into standard, callable function objects. This
article explains the exact role of std::mem_fn, how it
resolves member-pointer syntax limitations, and how it enables seamless
integration with standard library algorithms.
The Problem with Member Pointers
In C++, invoking a member function pointer directly requires the
pointer-to-member operators (.* or ->*).
Consequently, you cannot invoke a member function pointer using standard
function call syntax (f(args...)).
Consider this basic definition:
struct Widget {
void display() const;
};
void (Widget::*pmf)() const = &Widget::display;To call pmf, you must explicitly bind it to an
instance:
Widget w;
(w.*pmf)();
Widget* ptr = &w;
(ptr->*pmf)();Because of this requirement, standard library algorithms such as
std::for_each or std::transform, which expect
a regular callable object invoked via fn(arg), cannot
accept raw member function pointers directly.
The Purpose and Role of
std::mem_fn
Defined in the <functional> header,
std::mem_fn acts as a function adapter. When passed a
pointer to a member, it returns an unspecified callable function
object.
The returned wrapper provides three key capabilities:
- Uniform Callable Interface: It generates an
operator()overload, allowing the member pointer to be invoked with typical function call syntax:wrapped_fn(object, args...). - Polymorphic Argument Resolution: The first argument
can be an object reference (
w), a raw pointer (&w), or a smart pointer (std::unique_ptr<Widget>orstd::shared_ptr<Widget>). The adapter automatically determines whether to apply the.*operator or dereference the pointer viaoperator*. - Data Member Access: If passed a pointer to member
data,
std::mem_fnproduces a callable object that extracts that member variable when called, returning a reference to the data.
Practical Usage Example
Below is a practical scenario demonstrating how
std::mem_fn eliminates the need for boilerplate code when
working with containers and algorithms:
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
#include <functional>
struct Task {
int id;
void execute() const {
std::cout << "Executing task: " << id << '\n';
}
};
int main() {
std::vector<Task> tasks{{1}, {2}, {3}};
std::vector<std::shared_ptr<Task>> task_ptrs = {
std::make_shared<Task>(Task{4}),
std::make_shared<Task>(Task{5})
};
// Adapt the member function 'execute'
auto exec = std::mem_fn(&Task::execute);
// Call directly on a value
exec(tasks[0]);
// Call directly on a smart pointer without manual dereferencing
exec(task_ptrs[0]);
// Pass directly to standard algorithms
std::for_each(tasks.begin(), tasks.end(), std::mem_fn(&Task::execute));
std::for_each(task_ptrs.begin(), task_ptrs.end(), std::mem_fn(&Task::execute));
// Adapt a data member to extract values
auto get_id = std::mem_fn(&Task::id);
std::cout << "Extracted ID: " << get_id(tasks[0]) << '\n';
return 0;
}Context in Modern C++
Since C++17, utility functions like std::invoke natively
handle pointer-to-member invocations, and modern standard algorithms
(including C++20 Ranges) use projection mechanisms powered by
std::invoke. Similarly, generic lambda expressions
([](const auto& obj) { obj.execute(); }) can accomplish
the same task.
Despite these modern alternatives, std::mem_fn remains a
valuable tool. It explicitly states the intent to treat a member as a
self-contained callable object, avoids lambda boilerplate, and stores
uniform callables cleanly when passing member actions to older standard
library algorithms and components.