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.
Pointer to member function
A pointer to member function selects a method independently of any object. The full type encodes the class, the parameter types, the return type, and cv/ref qualifiers β making overload resolution unambiguous at the declaration site.
// C++98
struct Connection {
void open();
void close();
bool is_open() const;
int send(const char* buf, int len);
};
// Each method has a distinct pointer type
void (Connection::*pOpen)() = &Connection::open;
void (Connection::*pClose)() = &Connection::close;
bool (Connection::*pIsOpen)() const = &Connection::is_open;
int (Connection::*pSend)(const char*, int) = &Connection::send;
Connection c;
(c.*pOpen)(); // open
bool ok = (c.*pIsOpen)(); // query state
int n = (c.*pSend)("hi", 2); // send datacv-qualifiers are part of the type. A const-qualified pointer cannot be bound to a non-const method, and vice versa:
// C++11 β ref-qualifiers add yet another axis
struct Buffer {
void flush() &; // lvalue-ref-qualified
void flush() &&; // rvalue-ref-qualified
};
void (Buffer::*flushL)() & = &Buffer::flush; // binds lvalue overload
void (Buffer::*flushR)() && = &Buffer::flush; // binds rvalue overloadInvoking through std::invoke
std::invoke (C++17) is the idiomatic way to call any callable uniformly, including pointers to members. It dispatches correctly regardless of whether the object is passed by value, by reference, by pointer, or through a std::reference_wrapper.
#include <functional> // C++17
struct Timer {
void start();
void stop();
double elapsed() const;
double duration;
};
Timer t;
Timer* tp = &t;
// All four forms work without extra syntax
std::invoke(&Timer::start, t); // object by value/ref
std::invoke(&Timer::stop, tp); // object by raw pointer
std::invoke(&Timer::elapsed, std::cref(t)); // via reference_wrapper
std::invoke(&Timer::duration, t); // data member β returns double&In generic code, prefer std::invoke over the .*/->* operators so that lambdas, function pointers, functors, and pointers-to-member all work through the same call site:
// C++17 β generic dispatcher
template <typename Callable, typename Obj, typename... Args>
auto dispatch(Callable&& fn, Obj&& obj, Args&&... args)
-> std::invoke_result_t<Callable, Obj, Args...>
{
return std::invoke(std::forward<Callable>(fn),
std::forward<Obj>(obj),
std::forward<Args>(args)...);
}std::is_invocable and std::invoke_result_t from <type_traits> let you inspect callability at compile time without instantiating the call.
Storing pointers to members in containers
Containers can hold pointers to members just like any other value type. The idiomatic form is a std::vector or std::unordered_map keyed on a string or enum, creating a dispatch table that is data-driven rather than switch-driven.
#include <functional>
#include <string>
#include <vector>
#include <unordered_map> // C++11
struct Robot {
void moveForward();
void moveBack();
void turnLeft();
void turnRight();
};
using RobotCmd = void (Robot::*)();
const std::unordered_map<std::string, RobotCmd> kCommands = {
{"forward", &Robot::moveForward},
{"back", &Robot::moveBack},
{"left", &Robot::turnLeft},
{"right", &Robot::turnRight},
};
void execute(Robot& r, const std::string& cmd) {
auto it = kCommands.find(cmd);
if (it != kCommands.end())
(r.*(it->second))();
}For data members, a vector of field pointers can drive serialization or UI rendering loops:
// C++11
struct Config {
int width;
int height;
bool fullscreen;
int fps;
};
struct FieldDesc {
const char* name;
int Config::* ptr;
};
const std::vector<FieldDesc> kIntFields = {
{"width", &Config::width},
{"height", &Config::height},
{"fps", &Config::fps},
};
void print_ints(const Config& cfg) {
for (const auto& f : kIntFields)
std::printf(" %s = %d\n", f.name, cfg.*f.ptr);
}Pointer to member and inheritance
Pointer-to-member conversion is contravariant on the class hierarchy. A Base::* can be implicitly converted to Derived::* because every Derived object contains the Base subobject where the member lives. The reverse is not implicit and is generally unsafe.
// C++98
struct Base {
int value;
virtual void describe() const;
};
struct Derived : Base {
int extra;
};
int Base::* bv = &Base::value;
int Derived::* dv = bv; // Base::* β Derived::* β implicit, always safe
// Derived::* β Base::* requires explicit cast and is only valid if
// the concrete object actually has the extra field:
// int Base::* bv2 = static_cast<int Base::*>(&Derived::extra); // legal but dangerousWith virtual functions the pointer-to-member function type still works across the hierarchy, and virtual dispatch happens normally:
// C++11
struct Shape {
virtual double area() const = 0;
};
struct Circle : Shape {
double radius;
double area() const override;
};
struct Square : Shape {
double side;
double area() const override;
};
// pointer to Shape's virtual member function
double (Shape::*areaFn)() const = &Shape::area;
Circle c; c.radius = 3.0;
Square s; s.side = 4.0;
// virtual dispatch fires through the pointer-to-member
double ca = (c.*areaFn)(); // calls Circle::area()
double sa = (s.*areaFn)(); // calls Square::area()The virtual dispatch mechanism is fully preserved; the pointer-to-member selects the slot, not the implementation.
Pointer to member vs std::function
std::function offers type erasure at the cost of heap allocation and an indirect call; pointer-to-member is a zero-overhead compile-time value. Choosing between them depends on whether you know the exact class at compile time.
#include <functional> // C++11
struct Processor {
void run();
void run_fast();
};
Processor p;
// --- Pointer to member: zero overhead, no heap ---
void (Processor::*pm)() = &Processor::run;
(p.*pm)(); // direct or vtable call; no indirection through std::function
// --- std::function: flexible, type-erased ---
std::function<void()> fn = [&p]{ p.run(); };
fn(); // indirect call through function wrapperThe key differences:
pointer-to-member | std::function | |
|---|---|---|
| Object binding | separate β passed at call site | captured at construction |
| Overhead | zero | possible heap alloc + indirect call |
| Comparability | == / != supported | not equality-comparable |
| Nullability | nullptr, checks with bool | std::function{}, throws on null call |
| Generic code | needs std::invoke | directly callable with () |
std::mem_fn splits the difference: it wraps a pointer-to-member into a lightweight callable that still takes the object as its first argument, avoiding the heap allocation of std::function.
Reflection-like field access tables
A table of {name, pointer-to-member} pairs provides a simple, zero-dependency approach to runtime field access that resembles reflection. This pattern appears in serialization libraries, configuration parsers, and test-assertion helpers.
#include <cstdio>
#include <string_view>
#include <span> // C++20
#include <algorithm>
struct Vertex {
float x, y, z;
float nx, ny, nz; // normals
float u, v; // texture coords
};
struct FloatField {
std::string_view name;
float Vertex::* ptr;
};
constexpr FloatField kVertexFields[] = {
{"x", &Vertex::x}, {"y", &Vertex::y}, {"z", &Vertex::z},
{"nx", &Vertex::nx}, {"ny", &Vertex::ny}, {"nz", &Vertex::nz},
{"u", &Vertex::u}, {"v", &Vertex::v},
};
// Generic set-by-name β C++20
bool set_field(Vertex& vtx, std::string_view name, float val) {
auto it = std::ranges::find(kVertexFields, name, &FloatField::name);
if (it == std::end(kVertexFields)) return false;
vtx.*(it->ptr) = val;
return true;
}
float get_field(const Vertex& vtx, std::string_view name) {
auto it = std::ranges::find(kVertexFields, name, &FloatField::name);
return (it != std::end(kVertexFields)) ? vtx.*(it->ptr) : 0.0f;
}For heterogeneous field types, wrap the pointer in a std::variant or use a type-erased accessor function alongside the name. This approach scales to dozens of fields without code generation tools.
Offset-based tricks and offsetof
offsetof (from <cstddef>) gives the byte offset of a member within a standard-layout type, complementing pointer-to-member with a raw numeric equivalent. The two mechanisms are related but not interchangeable.
#include <cstddef> // C++98
struct Packet {
uint32_t header;
uint16_t length;
uint8_t flags;
uint8_t checksum;
};
static_assert(std::is_standard_layout_v<Packet>, "offsetof only safe for standard-layout");
constexpr size_t off_length = offsetof(Packet, length); // e.g. 4
constexpr size_t off_checksum = offsetof(Packet, checksum); // e.g. 7
// Reconstruct a member pointer from an offset via byte arithmetic
// (valid only for standard-layout types; otherwise UB)
Packet pkt{};
auto* base = reinterpret_cast<std::byte*>(&pkt);
auto* len_ptr = reinterpret_cast<uint16_t*>(base + off_length);
*len_ptr = 42; // writes pkt.lengthoffsetof is constexpr-friendly since C++17 for standard-layout types. It is used in serialization, memory-mapped I/O, and intrusive container implementations (the Linux container_of macro is the canonical example):
// C++17 β container_of idiom
template <typename T, typename M>
T* container_of(M* member_ptr, M T::* member_field) {
// Compute address of the enclosing object given a pointer to one of its members
const auto offset = reinterpret_cast<std::ptrdiff_t>(
&(reinterpret_cast<const T*>(0)->*member_field)
);
return reinterpret_cast<T*>(
reinterpret_cast<std::byte*>(member_ptr) - offset
);
}The container_of technique is safe only for standard-layout types and should be treated as a last resort; prefer storing the owning object pointer directly where possible.
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