Dennis Dennis - 2 months ago 14
C++ Question

Has or will C++14 or C++1z make it no longer undefined to call delegate class member function pointers?

Since this question seems to be causing some contention, I am editing it to first show the intent with hypothetical syntax, and then show an implementation. The implementation relies on a surprising type cast followed by a call of this type casted pointer. The problem is that the type cast is standard (though non-portable) C++, but calling its result is undefined behavior. My question concerns whether the standard has lately or may soon change the result of calling the type casted member function pointer to no longer be undefined behavior.

The intent is to be able to write code like:

void* object = ...; universal_mf_ptr mf_ptr = ...;
reinterpret_call(object, mf_ptr);


We assume the object is known "to the programmer" to be an instance of the same class as is pointed to by the member function pointer. However the class type is not known "to the compiler" at the call site. The type
universal_mf_ptr
is a placeholder for "pointer to a member function of any class type". The
reinterpret_call
is hypothetical syntax to tell the compiler "trust me, this call will be valid at runtime just push the address of object on the stack and emit an assembly instruction to call-indirect mf_ptr". It is so named in analogy to
reinterpret_cast
which tells the compiler "trust me, this cast is valid at runtime, just do the cast."

It turns out that, surprisingly,
universal_mf_ptr
is a real thing and in the standard and it is not undefined behavior. (According to the linked article below.) Member function pointers can be reinterpret_cast to other member function pointers (even of different/incompatible class types). However although it is standard it is not portable (i.e. not all compilers implement this part of the standard).

The undefined behavior comes into play when attempting to actually make use of (call) a
reinterpret_cast
'ed member function pointer. This is undefined behavior according to the standard, but (according to the linked article) is implemented on any compiler which implements the (non-portable, but standard) feature of casting member function pointers to unrelated class types. The author's assertion is that if the casting the pointer is in the standard so should calling the casted pointer.

In any case, should one wish to take advantage of the (standard, not undefined, but not portable) feature of casting member function pointers to a universal member function pointer type, for instance to store heterogeneous member functions in one collection, it is necessary to arbitrarily designate a "victim" class to be the target of type casts. This class need not have any such member function as it is being asserted to have, indeed it may have no members or be only forward declared and left undefined.

I suspect that it is this requirement to arbitrarily choose a victim class and assert that a member function pointer is of a class which it is not in fact a member of is what is causing this question to be down voted. Many of the arguments that this cannot be/should not be standard so as to call a member function this way could apply equally well to the cast, yet the latter is already in the standard.

The technique is described in this article, but it warns:


Casting between member function pointers is an extremely murky area. During the standardization of C++, there was a lot of discussion about whether you should be able to cast a member function pointer from one class to a member function pointer of a base or derived class, and whether you could cast between unrelated classes. By the time the standards committee made up their mind, different compiler vendors had already made implementation decisions which had locked them into different answers to these questions. According to the Standard (section 5.2.10/9), you can use reinterpret_cast to store a member function for one class inside a member function pointer for an unrelated class. The result of invoking the casted member function is undefined. The only thing you can do with it is cast it back to the class that it came from. I'll discuss this at length later in the article, because it's an area where the Standard bears little resemblance to real compilers.


Why would you want to do this? So that you can store member function pointers to many different classes of object in the same container and select one to call at runtime. (Assume that the code also keeps track at runtime which member function pointers are legal to call on which objects.)

class TypeEraser; // Not a base of anything.
typedef void (TypeEraser::*erased_fptr)();
map<string, erased_fptr> functions;

// Casting & storage as if member function of unrelated class is in the standard
functions["MyFunc"] = reinterpret_cast<erased_fptr>(&MyClass::MyFunc);

TypeEraser* my_obj = (TypeEraser*)(void*)new MyClass;
erased_fpr my_func = functions["MyFunc"];

// !!! But calling it is undefined behavior according to standard !!!
my_obj->*my_func();


Per the article linked above, on compilers where casting and storing the member function pointer is actually implemented, calling also works as expected. But (again, per the article) not all compilers actually implement casting and storage. That is, casting and storage is standard but it is not portable, while calling the member function pointer is not standard but works if the former works. It would be better if both were standard and portable.

And yes, there are several alternative ways to accomplish this same goal: lambdas, functors with a base class, etc. The place where all of these alternatives come up short is that they all cause the compiler to emit additional classes and members in the object file. You may personally not consider that a problem, but in a use case where a large number of member function pointers are being stored, that increases the size of the object file and the compile time much more than simply taking the address of the member functions.

Answer

No. The wording in in [expr.mptr.oper], as of N4606, reads:

The binary operator ->* binds its second operand, which shall be of type “pointer to member of T” to its first operand, which shall be of type “pointer to U” where U is either T or a class of which T is an unambiguous and accessible base class.

In the example my_obj->*my_func, T is TypeEraser and U is void, which does not satisfy the conditions, so the code is simply ill-formed. I am not aware of any proposal to change this.


For the new verison of the code, where you now use reinterpret_cast<TypeEraser*>(obj) instead so the types match... still no, as per [basic.lval]:

If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
(8.1) — the dynamic type of the object,
(8.2) — a cv-qualified version of the dynamic type of the object,
(8.3) — a type similar (as defined in 4.5) to the dynamic type of the object,
(8.4) — a type that is the signed or unsigned type corresponding to the dynamic type of the object,
(8.5) — a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
(8.6) — an aggregate or union type that includes one of the aforementioned types among its elements or nonstatic data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
(8.7) — a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
(8.8) — a char or unsigned char type.

TypeEraser is none of those things for MyClass, so it's undefined behavior.