Pointer-to-member-functions can be tricky

Note: the following applies to Microsoft’s compiler only — not to GCC or Clang.

Pointers-to-member-functions (PMFs) are a bit off the beaten track in C++. They aren’t very syntactically pleasing, and they aren’t as easy to deal with as regular pointers-to-free-functions (PFFs). But they still see use, particularly in pre-C++11 codebases or where people choose to avoid the overhead of std::function or lambdas as template arguments.

Although idiosyncratic, the way Microsoft’s compiler implements PMFs is fairly well-known in its effects. Classes that use single inheritance can achieve PMFs the same size as PFFs, i.e. the size of a pointer. Classes that use multiple inheritance must use an implementation-defined structure, which is larger than a pointer.

This snippet of code illustrates the difference.

This has all been well-documented on Raymond Chen’s blog, The Old New Thing, for years now. But given the scarcity of PMF usage, subtle dangers still lurk.

One issue can arise when separating the definition of a PMF from its class. Let’s say we have a class defined in a header a.h:

class A
  // probably a reasonable size class; we're going to use 
  // PMFs for this class
  // multiple inheritance is rare, and this class doesn't 
  // use it, so PMFs for this class will be just the size
  // of a pointer

Because A is a fairly large class, we might have a forward declaration for A, either in a conventionally-named header such as a_fwd.h, or just as a standalone forward declaration where pointers and references to A are used.

Then, in another file somewhere, we have a class that uses PMFs for A:

using A_PMF = auto (A::*)() -> void;
class B
  A_PMF a_pmf;
  // other stuff...  

A hard-to-diagnose bug lurks here. Do you see it?

We would assume that A_PMF is the size of a pointer. Generally, multiple inheritance is rarer than single inheritance, so this is nice, and what we normally expect from MSVC.

However, the size of A_PMF — and therefore the size of B — differs depending on whether A‘s class definition is known, or A is simply forward-declared!

If A is known, the compiler knows A is using single inheritance, and therefore A_PMF can be the size of a pointer. This is our normal expectation.

If A is forward-declared, we can still declare A_PMF. But now the compiler doesn’t know for sure that A_PMF can be the size of a pointer. A could use multiple inheritance, so the compiler must err on the side of caution and use the implementation-defined structure for A_PMF.

This can lead to B being two different sizes in two different translation units! We could have one where a.h is included (and A is known) and one where only a_fwd.h is included. Both versions will compile without warnings, but of course, depending on what comes after a_pmf in B, the bug may not show up immediately.

When it does show up, it may be difficult to diagnose because the debugger probably has perfect knowledge of A_PMF and will claim that it’s the size of a pointer, even when the translation unit you’re looking at compiled it differently. You can run into situations where you print a value, but when you break at the print statement, what prints is not what the debugger shows. This can be rather confusing, to say the least!

Perhaps the moral is: if you’re going to use PMFs, declare them right next to the class they belong with, and beware forward declarations.

Leave a Reply