2

Take the following testcase:

#include <iostream>

void foo()
{}

int main()
{
   std::cout << &foo << std::endl;
}

GCC 4.1.2, GCC 4.8 and GCC 4.9 (C++03 and C++11) all give the following output when building and then compiling:

$ g++ main.cpp -o test && ./test
main.cpp: In function 'int main()':
main.cpp:8:23: warning: the address of 'void foo()' will always evaluate as 'true' [-Waddress]
   std::cout << &foo << std::endl;
                 ^
1

This is supposedly because the only viable stream insertion for the function pointer is conversion-to-bool (and a cast to void* would be required to actually get an address into the stream).

However, Microsoft Visual Studio 2012 and 2013 output a pointer address instead.

Which set of toolchains is conformant? And is the non-conformance documented anywhere?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • [Related?](http://stackoverflow.com/questions/25538776/on-linux-printing-function-addresses-always-prints-1-c/25538912#25538912) – πάντα ῥεῖ Aug 28 '14 at 03:04
  • @πάνταῥεῖ: http://stackoverflow.com/questions/25538776/on-linux-printing-function-addresses-always-prints-1-c/25538912#comment39876982_25538776 – Lightness Races in Orbit Aug 28 '14 at 03:05
  • 2
    @LightnessRacesinOrbit You've already answered the question haven't you? The only viable conversion is to `bool` (4.12) and VS is incorrectly converting to `void*`, which should require a cast. – Praetorian Aug 28 '14 at 03:11
  • There is no implicit conversion from pointer to function to `void *`. – T.C. Aug 28 '14 at 03:11
  • 2
    @Praetorian: I haven't found the proof in the standard and/or documentation that says VS has an extension. That's what I'm after. Proof! :) – Lightness Races in Orbit Aug 28 '14 at 03:12
  • Have you compiled under `/W4`? You should get a "function pointer cast to data pointer" or similar warning. Or maybe I have that backwards. – Billy ONeal Aug 28 '14 at 03:12
  • @Praetorian Even with a cast, the conversion is only conditionally-supported. – T.C. Aug 28 '14 at 03:14
  • VS includes the source to its standard library implementation. You could look at that and/or the disassembly to see whether the MS compiler is calling the `void*` overload, or if MS has provided a function-pointer overload. – nobody Aug 28 '14 at 03:15
  • http://msdn.microsoft.com/en-us/library/9b1710d1.aspx – T.C. Aug 28 '14 at 03:17
  • @T.C. That doesn't say anything about implicit conversions or their non-standard-ness though – Lightness Races in Orbit Aug 28 '14 at 03:19
  • @LightnessRacesinOrbit That's in a section detailing the implicit conversions... – T.C. Aug 28 '14 at 03:19
  • @BillyONeal: I don't really know VS all that well, let alone its myriad options and whatnot. – Lightness Races in Orbit Aug 28 '14 at 03:20
  • @T.C. Oh okay. What unclear wording. "Can be converted" is hardly precise language, is it! :( Tsk MSDN. & it _still_ doesn't point out that this is non-standard behaviour! #ffs – Lightness Races in Orbit Aug 28 '14 at 03:20
  • @Lightness: I'm saying don't even bother using Visual Studio -- just drop to a VS command prompt and do `cl /W4 /EHsc your_test_file.cpp && your_test_file.exe` – Billy ONeal Aug 28 '14 at 03:21
  • @BillyONeal: Well Praetorian seems to have done it for me :D – Lightness Races in Orbit Aug 28 '14 at 03:22
  • @Lightness: Sure, `/Za` may work here. But `/Za` doesn't work under a lot of scenarios (e.g. you can't include `windows.h` with it turned on, for example). That's why I asked about `/W4` which just turns on more warnings. – Billy ONeal Aug 28 '14 at 03:22
  • @BillyONeal: That's okay — it functions as a demonstration that this is non-standard behaviour on VS's part, which is what I was after. I'd say the question of how to disable it without breaking everything else is something different: something more useful to many, I'm sure, but not to me as it turns out. :) At least now this fact is firmly documented here on SO. – Lightness Races in Orbit Aug 28 '14 at 03:24
  • [This SO answer](http://stackoverflow.com/a/23530445/434551) has details to explain why function pointer -> `bool` conversion is standards compliant. – R Sahu Aug 28 '14 at 04:11
  • @RSahu Yes but it doesn't show that there is no `operator<<` overload set for function pointers directly. :) – Lightness Races in Orbit Aug 28 '14 at 13:55
  • @LightnessRacesinOrbit, there is an `operator<<(void*)`. I don't understand why a pointer to a function converts to a `bool` instead of a `void*`. See http://stackoverflow.com/questions/25552822/why-does-pointer-to-int-convert-to-void-but-pointer-to-function-convert-to-bool. – R Sahu Aug 28 '14 at 15:42
  • @RSahu Regardless we know that it does and that isn't the question – Lightness Races in Orbit Aug 28 '14 at 16:30

2 Answers2

10

MSVC can be made to function correctly and perform the conversion from function pointer to bool if you disable language extensions (/Za switch). If you do that, your code produces the following warnings (at /W4 on VS2013)

1>main.cpp(8): warning C4305: 'argument' : truncation from 'void (*)(void)' to 'std::_Bool'
1>main.cpp(8): warning C4800: 'void (*)(void)' : forcing value to bool 'true' or 'false' (performance warning)

and the output is 1


This behavior is documented under the Casts section

Both the C++ compiler and C compiler support these kinds of non-ANSI casts:
...
Non-ANSI casts of a function pointer to a data pointer

Sure enough, the following line compiles only with /Za disabled

void *p = &foo;

Disabling language extensions produces the error message

1>main.cpp(8): error C2440: 'initializing' : cannot convert from 'void (*)(void)' to 'void *'
1>          There is no context in which this conversion is possible
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Hmm, that may be enough. Let me see what else pops up by morning but otherwise this is it I think. Thanks – Lightness Races in Orbit Aug 28 '14 at 03:19
  • It's a shame they don't document the non-standard nature of this behaviour. I wonder why... – Lightness Races in Orbit Aug 28 '14 at 13:54
  • @LightnessRacesinOrbit These are the same folks that [rolled back](https://connect.microsoft.com/VisualStudio/feedbackdetail/view/938122/list-initialization-inside-member-initializer-list-or-non-static-data-member-initializer-is-not-implemented) the brace-or-equal-initializer and list initialization in the mem-initializer features in VS2013 Update3, because they were buggy, instead of fixing it, and then left that little tidbit out of the release notes. I'm not surprised this is poorly documented. – Praetorian Aug 28 '14 at 16:26
  • Oh I never said I was surprised! Their documentation often [leaves something to be desired](http://msdn.microsoft.com/en-us/library/92d6x4xw.aspx). – Lightness Races in Orbit Aug 28 '14 at 16:32
6

At least by my reading of N3337, gcc is correct and MSVC is incorrect (unless you disable its extensions).

The path starts at §4 of the standard:

Standard conversions are implicit conversions with built-in meaning. Clause 4 enumerates the full set of such conversions.

So, the only standard conversions that exist are those listed in clause 4. Not every possible standard conversion can be applied in every situation though. Only those that fit together into a standard conversion sequence can be used. A standard conversion sequence is specified as follows:

— Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion.
— Zero or one conversion from the following set: integral promotions, floating point promotion, integral conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to member conversions, and boolean conversions.
— Zero or one qualification conversion.

Here we're starting from a pointer to a function, so the conversions under the first bullet point can't apply. We don't need/care about a qualification conversion, so we don't care about the third bullet point either.

To convert from pointer to function to pointer to void would clearly be a pointer conversion. These come in exactly three varieties. At §4.10/1 we have pointer conversions starting from null pointer constants (which clearly doesn't apply here). §4.10/2 covers conversions starting from:

A prvalue of type "pointer to cv T" where T is an object type [...]

That clearly doesn't apply here either, because a function isn't an object. The third option is:

A prvalue of type “pointer to cv D”, where D is a class type [...]

Again, a function isn't a class type, so that can't apply either.

That leaves us with only one option: a single conversion directly from "pointer to function" to "Boolean". That, of course, is a Boolean conversions. §4.12 says:

A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool.

So, our value can be converted to a Boolean if and only if 1) it's a prvalue, and 2) it's a pointer. That probably seems pretty obvious, but if we want to confirm, we can look to the definition of the address-of operator at §5.3.1/2 and 5.3.1/3:

The result of each of the following unary operators is a prvalue.

That fulfills the first requirement.

The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified-id. If the operand is a qualified-id naming a non-static member m of some class C with type T, the result has type “pointer to member of class C of type T” and is a prvalue designating C::m. Otherwise, if the type of the expression is T, the result has type “pointer to T” and is a prvalue that is the address of the designated object (1.7) or a pointer to the designated function. [emphasis added]

That clearly fulfills the second requirement--the result is a pointer.

Since those requirements have been met, the conversion can/will happen. The result of the conversion is as follows (back to §4.12):

A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true.

Since we started with a pointer to an actual function, we can't have a null pointer. That leaves only one possibility: "any other value is converted to true."

Precisely as the warning from gcc said, the only possible result of the conversion is a Boolean with the value true. That will print out as "1" by default, or "true" if boolalpha has been set to true.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • I guess I wasn't terribly clear about this, but I meant to ask about the `operator<<` overloads that MSVS supplies, and how compliant that list is. – Lightness Races in Orbit Aug 28 '14 at 16:33
  • 2
    @LightnessRacesinOrbit: The overload it's using is one that takes a `void *` parameter. That overload is required. The problem here is that although the overload must be present, it shouldn't be used because the conversion from `pointer to function` to `pointer to void` doesn't conform. – Jerry Coffin Aug 28 '14 at 16:42
  • 2
    Oh right so VS's non-conformance is not in supplying a non-standard `operator<<` for function pointers (in hindsight, this would be rather silly, even intractible pre-C++11) but in mangling the pointer conversion rules? – Lightness Races in Orbit Aug 28 '14 at 16:45
  • @LightnessRacesinOrbit: Correct. – Jerry Coffin Aug 28 '14 at 17:02