reinterpret_cast from rvalue to rvalue reference - c++

Just a simple does it compile test.
gcc accepts the following while clang and msvc reject it: https://godbolt.org/z/DlUasL
float test()
{
return reinterpret_cast<float&&>(0x7F800000);
}
Which one is right according to the standard?

The conversion this reinterpret_cast expression seeks to perform is not among the list of conversions [expr.reinterpret.cast] that a reinterpret_cast can perform [expr.reinterpret.cast]/1. 0x7F800000 is a literal of integral type. The only conversion reinterpret_cast could perform that converts from a value of integral type to some other type is that of turning such a value into a pointer type [expr.reinterpret.cast]/5. float&& is a reference type, not a pointer type. The only conversion reinterpret_cast can perform that converts to a reference type is that of converting a glvalue expression [expr.reinterpret.cast]/11. 0x7F800000 is not a glvalue. Thus, this code is ill-formed. The fact that GCC would accept this is quite surprising to me and, I would say, definitely a bug that should be reported…

Related

Why can't a prvalue of array type be converted to the same type using static_cast?

#include <iostream>
int main(){
using type = int[2];
static_cast<type>(type{1,2}); //#1
}
Clang and GCC both complain that #1 is ill-formed, and give weird diagnoses.
Clang reports
static_cast from 'int *' to 'type' (aka 'int [2]') is not allowed
GCC reports
invalid 'static_cast' from type 'type' {aka 'int [2]'} to type 'type' {aka 'int [2]'}
However, as per expr.static.cast#4
An expression E can be explicitly converted to a type T if there is an implicit conversion sequence ([over.best.ics]) from E to T
Isn't that converts a type to the same type be called identity conversion?
over.best.ics#general-8
If no conversions are required to match an argument to a parameter type, the implicit conversion sequence is the standard conversion sequence consisting of the identity conversion ([over.ics.scs]).
I think a crucial rule here is
The sequence of conversions is an implicit conversion as defined in [conv], which means it is governed by the rules for initialization of an object or reference by a single expression ([dcl.init], [dcl.init.ref]).
That means, assume a result object would be t which will be initialized by the prvalue.
type t = type{1,2}; // #2
#2 is also rejected by Clang and GCC. However, such a case should be caught by dcl.init.general#15.9
Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. A standard conversion sequence ([conv]) will be used, if necessary, to convert the initializer expression to the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed. When initializing a bit-field with a value that it cannot represent, the resulting value of the bit-field is implementation-defined.
According to basic.lval#1.2
A prvalue is an expression whose evaluation initializes an object or computes the value of an operand of an operator, as specified by the context in which it appears, or an expression that has type cv void.
In which case, Isn't that a prvalue of type type cannot initialize the result object?
Clang assumes that array-to-pointer conversion applies to the operand. However, such a conversion is only permitted if the bullet steps into expr.static.cast#8. In other words, the conversion should not apply here for the operand in bullet 4.
GCC gives a more nonreasonable diagnosis. I wonder why the explicit conversion is forbidden by Clang and GCC?
Raw arrays are difficult to keep in array form - in prvalue context an array decays to a pointer.
But does it apply static_cast context also? The rules are outlined in [expr.static.cast]...
We're not casting to a reference, so we skip the first 3 clauses and arrive at [expr.static.cast]/4:
An expression E can be explicitly converted to a type T if there is an implicit conversion sequence ([over.best.ics]) from E to T ...
This won't work because ([conv.general]/3):
An expression E can be implicitly converted to a type T if and only if the declaration
T t=E; is well-formed, for some invented temporary variable t
And int t[2] = int[2]{1, 2}; isn't well-formed. The only legal way to initialize an array is specified in [dcl.init.general]/15.5:
... if the destination type is an array, the object is initialized as follows. Let x1, …, xk be the elements of the expression-list. ... the ith array element is copy-initialized with xi for each 1 ≤ i ≤ k.
There is no provision for "unwrapping" int[2]{1, 2} into an expression-list here.
Otherwise, the result object is direct-initialized from E.
Similarly this won't work for the same reason that int x[2](int[2]{1, 2}); isn't well-formed.
Note that there is a special provision with regard to arrays in aggregates, which allows for easy copying (e.g. std::array). There is no such treatment in raw arrays.
We skip over 5. 6, 7 don't apply. Which brings us to [expr.static.cast]/8:
The lvalue-to-rvalue ([conv.lval]), array-to-pointer ([conv.array]), and function-to-pointer ([conv.func]) conversions are applied to the operand.
. . .
So that's it, the cast is invalid. Both Clang and GCC seem to be correct. The error reporting differs slightly because Clang reports the decayed type, and GCC reports the original type.
As a workaround, cast to a reference type: static_cast<type&&>(type{1,2});.

Can nullptr be converted to uintptr_t? Different compilers disagree

Consider this program:
#include <cstdint>
using my_time_t = uintptr_t;
int main() {
const my_time_t t = my_time_t(nullptr);
}
It failed to compile with msvc v19.24:
<source>(5): error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'my_time_t'
<source>(5): note: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type
<source>(5): error C2789: 't': an object of const-qualified type must be initialized
<source>(5): note: see declaration of 't'
Compiler returned: 2
but clang (9.0.1) and gcc (9.2.1) "eat" this code without any errors.
I like the MSVC behaviour, but is it confirmed by standard?
In other words is it bug in clang/gcc or it is possible to interpret
standard that this is right behaviour from gcc/clang?
In my opinion MSVC is not behaving standard-conform.
I am basing this answer on C++17 (draft N4659), but C++14 and C++11 have equivalent wording.
my_time_t(nullptr) is a postfix-expression and because my_time_t is a type and (nullptr) is a single expression in a parenthesized initializer list, it is exactly equivalent to an explicit cast expression. ([expr.type.conv]/2)
The explicit cast tries a few different specific C++ casts (with extensions), in particular also reinterpret_cast. ([expr.cast]/4.4) The casts tried before reinterpret_cast are const_cast and static_cast (with extensions and also in combination), but none of these can cast std::nullptr_t to an integral type.
But reinterpret_cast<my_time_t>(nullptr) should succeed because [expr.reinterpret.cast]/4 says that a value of type std::nullptr_t can be converted to an integral type as if by reinterpret_cast<my_time_t>((void*)0), which is possible because my_time_t = std::uintptr_t should be a type large enough to represent all pointer values and under this condition the same standard paragraph allows the conversion of void* to an integral type.
It is particularly strange that MSVC does allow the conversion if cast notation rather than functional notation is used:
const my_time_t t = (my_time_t)nullptr;
Although I can find no explicit mention in this Working Draft C++ Standard (from 2014) that conversion from std::nullptr_t to an integral type is forbidden, there is also no mention that such a conversion is allowed!
However, the case of conversion from std::nullptr_t to bool is explicitly mentioned:
4.12 Boolean conversions A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a
prvalue of type bool. A zero value, null pointer value, or null member
pointer value is converted to false; any other value is converted to
true. For direct-initialization (8.5), a prvalue of type
std::nullptr_t can be converted to a prvalue of type bool; the
resulting value is false.
Further, the only place in this draft document where conversion from std::nullptr_t to an integral type is mentioned, is in the "reinterpret_cast" section:
5.2.10 Reinterpret cast
...
(4) A pointer can be explicitly converted to any integral type large enough to hold it. The mapping function is
implementation-defined. [ Note: It is intended to be unsurprising to
those who know the addressing structure of the underlying machine. —
end note ] A value of type std::nullptr_t can be converted to an
integral type; the conversion has the same meaning and validity as a
conversion of (void*)0 to the integral type. [Note: A reinterpret_cast
cannot be used to convert a value of any type to the type
std::nullptr_t. — end note ]
So, from these two observations, one could (IMHO) reasonably surmise that the MSVC compiler is correct.
EDIT: However, your use of the "functional notation cast" may actually suggest the opposite! The MSVC compiler has no problem using a C-style cast in, for example:
uintptr_t answer = (uintptr_t)(nullptr);
but (as in your code), it complains about this:
uintptr_t answer = uintptr_t(nullptr); // error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'uintptr_t'
Yet, from the same Draft Standard:
5.2.3 Explicit type conversion (functional notation)
(1) A simple-type-specifier (7.1.6.2) or typename-specifier (14.6) followed
by a parenthesized expression-list constructs a value of the specified
type given the expression list. If the expression list is a single
expression, the type conversion expression is equivalent (in
definedness, and if defined in meaning) to the corresponding cast
expression (5.4). ...
The "corresponding cast expression (5.4)" can refer to a C-style cast.
All are standard conformant (ref. draft n4659 for C++).
nullptr is defined in [lex.nullptr] as:
The pointer literal is the keyword nullptr. It is a prvalue of type std::nullptr_t. [ Note: ..., a prvalue of this type is
a null pointer constant and can be converted to a null pointer value or null member pointer value.]
Even if notes are non normative, this one makes clear that for the standard, nullptr is expected to be converted to a null pointer value.
We later find in [conv.ptr]:
A null pointer constant is an integer literal with value zero or a prvalue of type std::nullptr_t. A
null pointer constant can be converted to a pointer type; .... A null pointer constant of integral type can
be converted to a prvalue of type std::nullptr_t.
Here again what is required by the standard is that 0 can be converted to a std::nullptr_t and that nullptr can be converted to any pointer type.
My reading is that the standard has no requirement on whether nullptr can be directly converted to an integral type or not. From that point on:
MSVC has a strict reading and forbid the conversion
Clang and gcc behaves as if an intermediary void * conversion was involved.

Why do lvalue-to-rvalue conversions exist? Why are they required? [duplicate]

I see the term "lvalue-to-rvalue conversion" used in many places throughout the C++ standard. This kind of conversion is often done implicitly, as far as I can tell.
One unexpected (to me) feature of the phrasing from the standard is that they decide to treat lvalue-to-rvalue as a conversion. What if they had said that a glvalue is always acceptable instead of a prvalue. Would that phrase actually have a different meaning? For example, we read that lvalues and xvalues are examples of glvalues. We don't read that lvalues and xvalues are convertible to glvalues. Is there a difference in meaning?
Before my first encounter with this terminology, I used to model lvalues and rvalues mentally more or less as follows: "lvalues are always able to act as rvalues, but in addition can appear on the left side of an =, and to the right of an &".
This, to me, is the intuitive behavior that if I have a variable name, then I can put that name everywhere where I would have put a literal. This model seems consistent with lvalue-to-rvalue implicit conversions terminology used in the standard, as long as this implicit conversion is guaranteed to happen.
But, because they use this terminology, I started wondering whether the implicit lvalue-to-rvalue conversion may fail to happen in some cases. That is, maybe my mental model is wrong here. Here is a relevant part of the standard: (thanks to the commenters).
Whenever a glvalue appears in a context where a prvalue is expected, the glvalue is converted to a prvalue; see 4.1, 4.2, and 4.3. [Note: An attempt to bind an rvalue reference to an lvalue is not such a context; see 8.5.3 .—end note]
I understand what they describe in the note is the following:
int x = 1;
int && y = x; //in this declaration context, x won't bind to y.
// but the literal 1 would have bound, so this is one context where the implicit
// lvalue to rvalue conversion did not happen.
// The expression on right is an lvalue. if it had been a prvalue, it would have bound.
// Therefore, the lvalue to prvalue conversion did not happen (which is good).
So, my question is (are):
1) Could someone clarify the contexts where this conversion can happen implicitly? Specifically, other than the context of binding to an rvalue reference, are there any other where lvalue-to-rvalue conversions fail to happen implicitly?
2) Also, the parenthetical [Note:...] in the clause makes it seem that we could have figured it out from the sentence before. Which part of the standard would that be?
3) Does that mean that rvalue-reference binding is not a context where we expect a prvalue expression (on the right)?
4) Like other conversions, does the glvalue-to-prvalue conversion involve work at runtime that would allow me to observe it?
My aim here is not to ask if it is desirable to allow such a conversion. I'm trying to learn to explain to myself the behavior of this code using the standard as starting point.
A good answer would go through the quote I placed above and explain (based on parsing the text) whether the note in it is also implicit from its text. It would then maybe add any other quotes that let me know the other contexts in which this conversion may fail to happen implicitly, or explain there are no more such contexts. Perhaps a general discussion of why glvalue to prvalue is considered a conversion.
I think the lvalue-to-rvalue conversion is more than just use an lvalue where an rvalue is required. It can create a copy of a class, and always yields a value, not an object.
I'm using n3485 for "C++11" and n1256 for "C99".
Objects and values
The most concise description is in C99/3.14:
object
region of data storage in the execution environment, the contents of which can represent
values
There's also a bit in C++11/[intro.object]/1
Some objects are polymorphic; the implementation generates information associated with
each such object that makes it possible to determine that object’s type during program execution. For other objects, the interpretation of the values found therein is determined by the type of the expressions used to access them.
So an object contains a value (can contain).
Value categories
Despite its name, value categories classify expressions, not values. lvalue-expressions even cannot be considered values.
The full taxonomy / categorization can be found in [basic.lval]; here's a StackOverflow discussion.
Here are the parts about objects:
An lvalue ([...]) designates a function or an object. [...]
An xvalue (an “eXpiring” value) also refers to an object [...]
A glvalue (“generalized” lvalue) is an lvalue or an xvalue.
An rvalue ([...]) is an xvalue, a temporary object or subobject thereof, or a value that is not associated with an object.
A prvalue (“pure” rvalue) is an rvalue that is not an xvalue. [...]
Note the phrase "a value that is not associated with an object". Also note that as xvalue-expressions refer to objects, true values must always occur as prvalue-expressions.
The lvalue-to-rvalue conversion
As footnote 53 indicates, it should now be called "glvalue-to-prvalue conversion". First, here's the quote:
1 A glvalue of a non-function, non-array type T can be converted to a prvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the glvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program
that necessitates this conversion has undefined behavior. If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.
This first paragraph specifies the requirements and the resulting type of the conversion. It isn't yet concerned with the effects of the conversion (other than Undefined Behaviour).
2 When an lvalue-to-rvalue conversion occurs in an unevaluated operand or a subexpression thereof the value contained in the referenced object is not accessed. Otherwise, if the glvalue has a class type, the conversion copy-initializes a temporary of type T from the glvalue and the result of the conversion is a prvalue for the temporary. Otherwise, if the glvalue has (possibly cv-qualified) type std::nullptr_t, the
prvalue result is a null pointer constant. Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.
I'd argue that you'll see the lvalue-to-rvalue conversion most often applied to non-class types. For example,
struct my_class { int m; };
my_class x{42};
my_class y{0};
x = y;
The expression x = y does not apply the lvalue-to-rvalue conversion to y (that would create a temporary my_class, by the way). The reason is that x = y is interpreted as x.operator=(y), which takes y per default by reference, not by value (for reference binding, see below; it cannot bind an rvalue, as that would be a temporary object different from y). However, the default definition of my_class::operator= does apply the lvalue-to-rvalue conversion to x.m.
Therefore, the most important part to me seems to be
Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.
So typically, an lvalue-to-rvalue conversion will just read the value from an object. It isn't just a no-op conversion between value (expression) categories; it can even create a temporary by calling a copy constructor. And the lvalue-to-rvalue conversion always returns a prvalue value, not a (temporary) object.
Note that the lvalue-to-rvalue conversion is not the only conversion that converts an lvalue to a prvalue: There's also the array-to-pointer conversion and the function-to-pointer conversion.
values and expressions
Most expressions don't yield objects[[citation needed]]. However, an id-expression can be an identifier, which denotes an entity. An object is an entity, so there are expressions which yield objects:
int x;
x = 5;
The left hand side of the assignment-expression x = 5 also needs to be an expression. x here is an id-expression, because x is an identifier. The result of this id-expression is the object denoted by x.
Expressions apply implicit conversions: [expr]/9
Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue, array-to-pointer, or function-to-pointer standard conversions are applied to convert the expression to a prvalue.
And /10 about usual arithmetic conversions as well as /3 about user-defined conversions.
I'd love now to quote an operator that "expects a prvalue for that operand", but cannot find any but casts. For example, [expr.dynamic.cast]/2 "If T is a pointer type, v [the operand] shall be a prvalue of a pointer to complete class type".
The usual arithmetic conversions required by many arithmetic operators do invoke an lvalue-to-rvalue conversion indirectly via the standard conversion used. All standard conversions but the three that convert from lvalues to rvalues expect prvalues.
The simple assignment however doesn't invoke the usual arithmetic conversions. It is defined in [expr.ass]/2 as:
In simple assignment (=), the value of the expression replaces that of the object referred to by the left operand.
So although it doesn't explicitly require a prvalue expression on the right hand side, it does require a value. It is not clear to me if this strictly requires the lvalue-to-rvalue conversion. There's an argument that accessing the value of an uninitialized variable should always invoke undefined behaviour (also see CWG 616), no matter if it's by assigning its value to an object or by adding its value to another value. But this undefined behaviour is only required for an lvalue-to-rvalue conversion (AFAIK), which then should be the only way to access the value stored in an object.
If this more conceptual view is valid, that we need the lvalue-to-rvalue conversion to access the value inside an object, then it'd be much easier to understand where it is (and needs to be) applied.
Initialization
As with simple assignment, there's a discussion whether or not the lvalue-to-rvalue conversion is required to initialize another object:
int x = 42; // initializer is a non-string literal -> prvalue
int y = x; // initializer is an object / lvalue
For fundamental types, [dcl.init]/17 last bullet point says:
Otherwise, the initial value of the object being initialized is the (possibly converted) value of the initializer expression. Standard conversions will be used, if necessary, to convert the initializer expression to the cv-unqualified version of the destination type; no user-defined conversions are considered. If the conversion cannot be done, the initialization is ill-formed.
However, it also mentioned the value of the initializer expression. Similar to the simple-assignment-expression, we can take this as an indirect invocation of the lvalue-to-rvalue conversion.
Reference binding
If we see lvalue-to-rvalue conversion as a way to access the value of an object (plus the creation of a temporary for class type operands), we understand that it's not applied generally for binding to a reference: A reference is an lvalue, it always refers to an object. So if we bound values to references, we'd need to create temporary objects holding those values. And this is indeed the case if the initializer-expression of a reference is a prvalue (which is a value or a temporary object):
int const& lr = 42; // create a temporary object, bind it to `r`
int&& rv = 42; // same
Binding a prvalue to an lvalue reference is prohibited, but prvalues of class types with conversion functions that yield lvalue references may be bound to lvalue references of the converted type.
The complete description of reference binding in [dcl.init.ref] is rather long and rather off-topic. I think the essence of it relating to this question is that references refer to objects, therefore no glvalue-to-prvalue (object-to-value) conversion.
On glvalues: A glvalue ("generalized" lvalue) is an expression that is either an lvalue or an xvalue.
A glvalue may be implicitly converted to prvalue with lvalue-to-rvalue, array-to-pointer, or function-to-pointer implicit conversion.
Lvalue transformations are applied when lvalue argument (e.g. reference to an object) is used in context where rvalue (e.g. a number) is expected.
Lvalue to rvalue conversion
A glvalue of any non-function, non-array type T can be implicitly converted to prvalue of the same type. If T is a non-class type, this conversion also removes cv-qualifiers. Unless encountered in unevaluated context (in an operand of sizeof, typeid, noexcept, or decltype), this conversion effectively copy-constructs a temporary object of type T using the original glvalue as the constructor argument, and that temporary object is returned as a prvalue. If the glvalue has the type std::nullptr_t, the resulting prvalue is the null pointer constant nullptr.

The address of a function matching a bool vs const void* overload

I'm reading Unexpected value using random number generator as a function in C++ and the comments and current answer say that the user is outputting the address of the function. That sounded reasonable. I assumed that a function-to-pointer conversion was occurring and therefore matching the const void* overload, however upon testing it myself, I get different results in GCC/Clang vs MSVC. The following test program:
#include <iostream>
void test()
{
}
void func(bool)
{
std::cout << "bool";
}
void func(const void*)
{
std::cout << "const void*";
}
int main()
{
func(test);
}
outputs bool in GCC/Clang (coliru)
and const void* in MSVC (rextester warning live collaboration link)
N3337 says:
[conv.func]
An lvalue of function type T can be converted to a prvalue of type
"pointer to T." The result is a pointer to the function.
[conv.bool]
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to
member type can be converted to a prvalue of type bool. A zero
value, null pointer value, or null member pointer value is converted
to false; any other value is converted to true. A prvalue of type
std::nullptr_t can be converted to a prvalue of type bool; the
resulting value is false.
So a pointer which is not a null pointer value converted to bool should equal true, explaining the warning given by GCC/Clang.
Then Table 12 Conversions under [over.ics.scs] gives a function-to-pointer conversion an "Exact Match" rank and boolean conversions "Conversion" rank. [over.ics.rank]/4 then says:
Standard conversion sequences are ordered by their ranks: an Exact
Match is a better conversion than a Promotion, which is a better
conversion than a Conversion. Two conversion sequences with the same
rank are indistinguishable unless one of the following rules applies:
— A conversion that does not convert a pointer, a pointer to member,
or std::nullptr_t to bool is better than one that does.
— [...]
I am not a language lawyer so I hope that I quoted the right sections.
However MSVC will call the const void* overload even if the bool overload is absent, and vice versa: GCC/Clang will call the bool overload even if the const void* overload is absent. So I'm not clear on the conversions here. Can somebody clear this up for me?
Seems like a bug (or extension) in MSVC. The 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.
Together with 3.9/8 [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.
(emphasis mine)
Using /Za to disable extensions will disable the non-standard conversion.

Is Visual Studio buggy in printing the function address?

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