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++98A 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
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 pointerThe declaration form is T Class::*name β a pointer to a member of Class with type T.
Pointer to member function
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
LogFn fn = nullptr;
if (fn) { (log.*fn)("msg"); } // contextually converts to boolStatic 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:
struct Factory {
static Widget* create();
};
Widget* (*creator)() = &Factory::create; // regular function pointer, not pointer-to-memberExamples
Dispatch table over a fixed method set
#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:
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 == 0The field selection is fully resolved at compile time β the compiler can inline the access with zero overhead.
Member function as template parameter
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:
#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:
#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++17A generic call wrapper built on std::invoke accepts any callable without special-casing pointer-to-member:
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:
// 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:
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:
CmdFn fn = nullptr;
(cmd.*fn)(); // undefined behaviorIgnoring cv-qualifiers on member function types. A const member function has a distinct pointer type. Assignment across the qualifier boundary is a compile error:
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-qualifiedTaking 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 callablestd::invoke/std::invoke_result_tβ C++17 uniform invocation covering all callable categoriesstd::functionβ C++11 type-erased callable; stores pointer-to-member when paired withstd::bindor a lambda capture- Compile-Time Dispatch β uses pointer-to-member as a template non-type parameter for zero-overhead static dispatch