To my understanding of the standard regarding [expr.reinterpret.cast]—but please correct me if I'm wrong—converting a pointer to member function to some other pointer to member function and back to its original is actually legal, well-defined behavior. As laid out in section 7.6.1.10:
A prvalue of type “pointer to member of X of type T1” can be explicitly converted to a prvalue of a different type “pointer to member of Y of type T2” if T1 and T2 are both function types or both object types.59 The null member pointer value ([conv.mem]) is converted to the null member pointer value of the destination type. The result of this conversion is unspecified, except in the following cases:
(10.1) Converting a prvalue of type “pointer to member function” to a different pointer-to-member-function type and back to its original type yields the original pointer-to-member value.
(10.2) Converting a prvalue of type “pointer to data member of X of type T1” to the type “pointer to data member of Y of type T2” (where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer-to-member value.
This seems to be contrary to regular function pointers, where doing the same conversions would result in unspecified behavior. As laid out in section 7.6.1.6:
A function pointer can be explicitly converted to a function pointer of a different type. [Note 4: The effect of calling a function through a pointer to a function type ([dcl.fct]) that is not the same as the type used in the definition of the function is undefined ([expr.call]). — end note] Except that converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are function types) and back to its original type yields the original pointer value, the result of such a pointer conversion is unspecified. [Note 5: See also [conv.ptr] for more details of pointer conversions. — end note]
Some example code to illustrate the distinction between the two types of conversions:
struct s { auto f() -> void {} };
struct t { auto g(t) -> t { return {}; } };
auto f() -> void {};
auto g(t) -> t { return {}; }
auto main() -> int {
auto ps = &s::f;
auto pt = reinterpret_cast<decltype(&t::g)>(ps);
(s{}.*reinterpret_cast<decltype(&s::f)>(pt))(); // well-defined behavior
auto pf = &f;
auto pg = reinterpret_cast<decltype(&g)>(pf);
reinterpret_cast<decltype(&f)>(pg)(); // unspecified behavior
}
What is the reason behind having the regular function pointer conversions result in unspecified behavior? Why aren't both types of conversions either well-defined or unspecified? What are the ramifications of allowing regular function pointer conversions to be well-defined as well? It strikes me as quite odd to have this distinction when both types of conversions appear to be of such a similar nature.