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:

  1. Uniform Callable Interface: It generates an operator() overload, allowing the member pointer to be invoked with typical function call syntax: wrapped_fn(object, args...).
  2. Polymorphic Argument Resolution: The first argument can be an object reference (w), a raw pointer (&w), or a smart pointer (std::unique_ptr<Widget> or std::shared_ptr<Widget>). The adapter automatically determines whether to apply the .* operator or dereference the pointer via operator*.
  3. Data Member Access: If passed a pointer to member data, std::mem_fn produces 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.