17
votes

This is a tangential follow up to my previous question The address of a function matching a bool vs const void* overload. The answerer explained:

The [C++11] standard does not define any standard conversions from a "pointer to function" to a "pointer to void."

It's hard to provide a quote for the absence of something, but the closest I can do is C++11 4.10/2 [conv.ptr]:

A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8) of type T (that is, not a base class subobject). The null pointer value is converted to the null pointer value of the destination type.

(emphasis mine)

Assuming func is declared void func();, if you do a C-style cast, i.e. (void*) func, the cast will be successful. static_cast<void*>(func) however is invalid, but reinterpret_cast<void*>(func) will be successful. What you cannot do however is convert the subsequently converted pointer back to its original type. For example,

Fine:

int main() {
  int* i;
  void* s = static_cast<void*>(i);
  i = static_cast<int*>(s);
  s = reinterpret_cast<void*>(i);
  i = reinterpret_cast<int*>(s);
}

Not fine:

void func() { }

int main() {
  void* s = reinterpret_cast<void*>(func);
  reinterpret_cast<decltype(func)>(s);
}

N3337 starts off by saying,

[expr.reinterpret.cast]

The result of the expression reinterpret_cast<T>(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue and the lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the expression v. Conversions that can be performed explicitly using reinterpret_cast are listed below. No other conversion can be performed explicitly using reinterpret_cast.

I bolded the language that I believe is key here. The last part seems to imply that if the conversion is not listed, it's illegal. In brief summary, the allowed conversions are:

  • A pointer can be explicitly converted to any integral type large enough to hold it.
  • A value of integral type or enumeration type can be explicitly converted to a pointer.
  • A function pointer can be explicitly converted to a function pointer of a different type.
  • An object pointer can be explicitly converted to an object pointer of a different type.
  • Converting a function pointer to an object pointer type or vice versa is conditionally-supported.
  • The null pointer value (4.10) is converted to the null pointer value of the destination type.
  • 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.
  • An lvalue expression of type T1 can be cast to the type "reference to T2" if an expression of type "pointer to T1" can be explicitly converted to the type "pointer to T2" using a reinterpret_cast.

void* is not a function pointer and objects don't have function or void type.

[basic.types]

An object type is a (possibly cv-qualified) type that is not a function type, not a reference type, and not a void type.

So maybe I'm grasping at straws, but it seems reinterpret_cast<void*>(func) is illegal. However, on the other hand, [expr.static.cast]/5 says "Otherwise, the static_cast shall perform one of the conversions listed below. No other conversion shall be performed explicitly using a static_cast." the key difference being "shall" and "can". Is this enough to make the reinterpret_cast legal or am I missing something else?

1
This works if you define a function pointer type (demo).Sergey Kalinichenko
@dasblinkenlight Yes, that's explained in my answer.Columbo
@Columbo func = ... was irrelevant to the question since you can't assign to a function. Anyway, it appears that your deleted answer was correct. v is decayed, but decltype(func) isn't. Right?user3920237
@remyabel Firstly it isn't deleted anymore, secondly: Yes, that is correct :)Columbo

1 Answers

20
votes

(All quotes are from N3337 and are equivalent for every single draft until N4296 from there on, i.e. this answer is valid at least for C++11 and C++14 but not for C++03 as the first quote of this answer does not exist in there.)

[expr.reinterpret.cast]/8:

Converting a function pointer to an object pointer type or vice versa is conditionally-supported. The meaning of such a conversion is implementation-defined, except that if an implementation supports conversions in both directions, converting a prvalue of one type to the other type and back, possibly with different cv-qualification, shall yield the orignal pointer value.

This is contained in your listing. You argue that void is not an object type, but you didn't consider the crucial [basic.compound]/3:

The type of a pointer to void or a pointer to an object type is called an object pointer type.

(That is, a object pointer type is not necessarily a "pointer to object type" - standard terminology got you there.)

The only reason that

f = reinterpret_cast<decltype(f)>(s);

Isn't fine on GCC or Clang is that the target type, as opposed to the source expression, is not decayed - and you can clearly not cast void* to a function type. You need to make the target type a pointer to function, then it works.