Reinterpret cast conversions

Viewed 63

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.

1 Answers

You simply misread the second quote. In the highlighted sentence everything in the subordinate clause before the main clause "the result of such a pointer conversion is unspecified" is stating an exception to that main statement.

The reinterpret_cast round-trip conversion guarantee works for function pointers exactly like it does for member function pointers (but not for object pointers or data member pointers, where it is restricted by alignment).

So both of the examples you give result in specified function/member pointer values after the back-reinterpret_cast and the program has well-defined behavior.

The second quote is simply using a more compact wording to say that aside from this use of the result of the conversion, nothing else is specified about reinterpret_cast<decltype(&g)>(pf).

The same is true for reinterpret_cast<decltype(&t::g)>(ps). The relevant statement for this is in the first quote before the highlighted section: "The result of this conversion is unspecified, except in the following cases:"

Related