Skip to content
C++
Idiom
since C++98
Intermediate

Pointer to Member

A typed handle to a specific non-static member of a class, invoked against any instance of that class using the .* and ->* operators.

Pointer to Membersince C++98

A pointer-to-member is a typed value that identifies a specific non-static data member or non-static member function of a class, independently of any object instance, and is applied to an instance at the call site using .* or ->*.

Overview

Pointer-to-member is a distinct type category in C++, separate from ordinary object pointers and function pointers. It encodes both the type of the member and the class it belongs to; it cannot be implicitly converted to void* or any other pointer type. A pointer-to-member carries no object with it β€” it is closer to an offset or a selector than a raw address.

Two dedicated operators dereference pointer-to-members:

  • .* β€” when the object is available as a value or reference
  • ->* β€” when the object is accessed through a pointer

Neither operator is overloadable.

There are two sub-categories:

  • Pointer to data member β€” selects a field by name, decoupled from any instance
  • Pointer to member function β€” selects a method by name and signature, decoupled from any instance

Both are first-class values: they can be stored in variables, passed to functions, returned from functions, and used as non-type template parameters.

Syntax

Pointer to data member

cpp
struct Point {
    double x;
    double y;
};

double Point::*px = &Point::x;  // pointer to data member
double Point::*py = &Point::y;

Point  p{3.0, 4.0};
Point* pp = &p;

double vx = p.*px;    // 3.0 β€” via object
double vy = pp->*py;  // 4.0 β€” via pointer

The declaration form is T Class::*name β€” a pointer to a member of Class with type T.

Pointer to member function

cpp
struct Logger {
    void info(const std::string& msg);
    void warn(const std::string& msg);
    void error(const std::string& msg);
};

using LogFn = void (Logger::*)(const std::string&);

LogFn fn = &Logger::warn;  // & is mandatory β€” unlike non-member functions

Logger  log;
Logger* lp = &log;

(log.*fn)("disk nearly full");   // parens required β€” see pitfalls
(lp->*fn)("disk nearly full");

The & before Logger::warn is not optional. For non-member functions the address-of operator is elided by an implicit conversion; for member functions it is not β€” the compiler must know you want the address rather than a call.

Null pointer-to-member

cpp
LogFn fn = nullptr;
if (fn) { (log.*fn)("msg"); }  // contextually converts to bool

Static members are excluded

Pointers-to-member cannot refer to static members. A static member function is an ordinary function; its address is an ordinary function pointer:

cpp
struct Factory {
    static Widget* create();
};

Widget* (*creator)() = &Factory::create;  // regular function pointer, not pointer-to-member

Examples

Dispatch table over a fixed method set

cpp
#include <functional>
#include <string>
#include <unordered_map>

struct Command {
    void execute();
    void undo();
    void preview();
};

using CmdFn = void (Command::*)();

const std::unordered_map<std::string, CmdFn> kDispatch = {
    {"execute", &Command::execute},
    {"undo",    &Command::undo},
    {"preview", &Command::preview},
};

void invoke_by_name(Command& cmd, const std::string& action) {
    auto it = kDispatch.find(action);
    if (it != kDispatch.end())
        (cmd.*(it->second))();
}

The table is data β€” new entries require no changes to control flow.

Non-type template parameter

Pointer-to-member has been valid as a non-type template parameter since C++98. The pointer must be a non-overloaded constant expression. C++20 relaxed the broader rules for structural types but pointer-to-member has always been permitted:

cpp
template <typename T, int T::*Field>
void zero_field(T& obj) {
    obj.*Field = 0;
}

struct Vec3 { int x, y, z; };

Vec3 v{1, 2, 3};
zero_field<Vec3, &Vec3::x>(v);  // v.x == 0
zero_field<Vec3, &Vec3::z>(v);  // v.z == 0

The field selection is fully resolved at compile time β€” the compiler can inline the access with zero overhead.

Member function as template parameter

cpp
template <typename T, void (T::*Action)()>
struct Deferred {
    T* target;
    void run() { (target->*Action)(); }
};

struct Worker {
    void start();
    void stop();
};

Worker w;
Deferred<Worker, &Worker::start> d{&w};
d.run();  // calls w.start()

std::mem_fn β€” wrap into a regular callable (C++11)

std::mem_fn (in <functional>) wraps a pointer-to-member into a callable that accepts the object as its first argument, making it compatible with standard algorithms:

cpp
#include <algorithm>
#include <functional>
#include <vector>

struct Task {
    bool is_done() const;
};

std::vector<Task> tasks = { /* ... */ };

// Erase completed tasks β€” C++11
tasks.erase(
    std::remove_if(tasks.begin(), tasks.end(), std::mem_fn(&Task::is_done)),
    tasks.end()
);

std::invoke β€” uniform call syntax (C++17)

Since C++17, std::invoke handles all callable types uniformly, including pointers-to-member. This is the preferred mechanism in generic code:

cpp
#include <functional>

struct Sensor {
    double read() const;
    double calibrate(double offset);
    double value;
};

Sensor s;

double v1 = std::invoke(&Sensor::read, s);            // member function via object   β€” C++17
double v2 = std::invoke(&Sensor::calibrate, &s, 1.5); // member function via pointer  β€” C++17
double v3 = std::invoke(&Sensor::value, s);            // data member                  β€” C++17

A generic call wrapper built on std::invoke accepts any callable without special-casing pointer-to-member:

cpp
template <typename F, typename... Args>
auto call(F&& f, Args&&... args)
    -> std::invoke_result_t<F, Args...>  // C++17
{
    return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}

Best Practices

Use std::invoke in generic code. Any template that accepts a callable should invoke it via std::invoke rather than callable(args...). Pointer-to-members are not directly callable with (), so code that omits this will silently reject them.

Use using aliases for complex types. Pointer-to-member type syntax becomes unreadable with const, noexcept, or ref-qualifiers attached:

cpp
// Readable:
using EventHandler = void (EventLoop::*)(int fd, uint32_t events);

// Unreadable at scale:
void (EventLoop::*handler)(int fd, uint32_t events) = &EventLoop::on_readable;

Understand the conversion direction. Pointer-to-member conversion is contravariant on the class: a Base::* converts implicitly to Derived::*, which is the reverse of what regular pointers do. This is correct β€” a derived object has all base members, so a pointer-to-base-member can be applied to a derived object:

cpp
struct Base    { int x; };
struct Derived : Base { int y; };

int Base::*    bx = &Base::x;
int Derived::* dx = bx;        // implicit: Base::* β†’ Derived::* βœ“
// int Base::* bx2 = &Derived::y; // error: Derived::* β†’ Base::* is not implicit βœ—

Common Pitfalls

Forgetting parentheses around .* expressions. The .* and ->* operators have lower precedence than the function call (). Without parentheses, obj.*fn(arg) parses as obj.*(fn(arg)) β€” a type error at best, silent misparse at worst. Always write (obj.*fn)(arg).

Assuming pointer-to-member is a pointer. It is not. It cannot be stored in void*, printed as an integer address, or cast to uintptr_t. Its size is implementation-defined and may exceed sizeof(void*), particularly in the presence of virtual functions or multiple inheritance, where the implementation may need to store a vtable offset alongside the function pointer.

Calling through a null pointer-to-member. Undefined behavior, the same as any null dereference:

cpp
CmdFn fn = nullptr;
(cmd.*fn)();  // undefined behavior

Ignoring cv-qualifiers on member function types. A const member function has a distinct pointer type. Assignment across the qualifier boundary is a compile error:

cpp
struct Foo {
    int  get() const;
    void set(int);
};

int (Foo::*pg)() const = &Foo::get;  // ok
// int (Foo::*ps)() const = &Foo::set;  // error: set is not const-qualified

Taking the address of a static member function. This is a compile error. Static members are not instance-bound; use a plain function pointer instead.

See Also

  • std::mem_fn β€” C++11 adapter that wraps a pointer-to-member into a regular callable
  • std::invoke / std::invoke_result_t β€” C++17 uniform invocation covering all callable categories
  • std::function β€” C++11 type-erased callable; stores pointer-to-member when paired with std::bind or a lambda capture
  • Compile-Time Dispatch β€” uses pointer-to-member as a template non-type parameter for zero-overhead static dispatch