17

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?

Community
  • 1
  • 1
  • This works if you define a function pointer type ([demo](http://ideone.com/HNqcHh)). – Sergey Kalinichenko Dec 01 '14 at 13:45
  • @dasblinkenlight Yes, that's explained in my answer. – Columbo Dec 01 '14 at 13:45
  • @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? –  Dec 01 '14 at 13:48
  • 1
    @remyabel Firstly it isn't deleted anymore, secondly: Yes, that is correct :) – Columbo Dec 01 '14 at 13:51

1 Answers1

20

(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.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 5
    Just to add to this fine answer, (1) the conditional support for converting between object and function pointers is very recent (C++11 or C++14); (2) but a requirement for such conversion has been part of Posix for a long time, and essential in the Windows API, so in essence the language has been catching up to reality here; (3) the separation of object pointers from functions pointers is tied to the Harvard architecture, which is still in use. – Cheers and hth. - Alf Dec 01 '14 at 13:47
  • please do state which standard or draft you refer to. – Cheers and hth. - Alf Dec 01 '14 at 13:49
  • @Cheersandhth.-Alf About the C++11 part: Yes, I didn't explicitly mention that since the question is tagged with C++11 anyway. I edited the answer, I used N3337. – Columbo Dec 01 '14 at 13:50
  • Well at least the first quote was the same in C++11 (using N3290, the final draft). So, it entered in C++11. – Cheers and hth. - Alf Dec 01 '14 at 13:56
  • @Cheersandhth.-Alf The final draft of C++11 is N3337. And yes, I didn't find the quote in the C++03 FD, and added that to the head-note (is that the valid term?). – Columbo Dec 01 '14 at 13:57
  • I don't think so, since N3337 is dated 16th january 2012. ;-) – Cheers and hth. - Alf Dec 01 '14 at 13:57
  • Man that's really sneaky. :( –  Dec 01 '14 at 14:00
  • @Cheersandhth.-Alf As far as I'm concerned, N3337 only contains editorial changes, making it equivalent in content but more pleasing to the eye. – Columbo Dec 01 '14 at 14:00
  • @Columbo Doesn't that support exactly what Alf is claiming? Just because the changes are minor, and just because the meaning is the same, that doesn't mean there are no changes between C++11 and N3337. It's probably okay to use N3337 as a reference for C++11, but if someone takes the time to check what C++11 says, that's even better. –  Dec 01 '14 at 14:10
  • @hvd Yeah, I somehow thought that he implied that N3337 isn't a valid document to refer to C++11.. – Columbo Dec 01 '14 at 14:18
  • @hvd There are no "changes". The standard is not a PDF file, it's a set of rules. If those rules didn't change, neither has the standard, and both N3337 and N3296 refer to the same rule set (=standard). – Columbo Dec 01 '14 at 17:57
  • @Columbo I don't follow. The standard is a text available in various forms, including PDF, and the exact wording of that text is an essential part of the standard. If you have a different text with different words, even if they mean the same thing, you don't have the standard. –  Dec 01 '14 at 18:07
  • @hvd Yes, but an editorial change is not allowed to change the exact meaning of the wording, is it? (Regardless of whether this makes a difference now). – Columbo Dec 01 '14 at 18:26
  • @Columbo: Well my “I don’t think so” was in reference to the claim that a draft from 2012 could be the latest draft of C++11. It’s clearly not a draft of C++11: but yes, it’s a cleaned up version of C++11. I think you can safely use it as if it were actually the C++11 standard, with one exception that I see, namely that the editor removed (an unintended) `const` from the `&&` argument of the `scoped_allocator_adaptor` constructor. – Cheers and hth. - Alf Dec 02 '14 at 02:23