What's the difference between &C::c and &(C::c)?
Test code in below and I put the output info in comment. I was using gcc 4.8.5 and Centos 7.2.
#include <iostream>
#include <cstdio>
class C
{
public:
void foo() {
printf("%p, %p\n", &C::c, &(C::c)); // output value is 0x4, 0x7ffc2e7f52e8
std::cout << &C::c << std::endl; // output value is 1
}
int a;
int c;
};
int main(void)
{
C co;
printf("%p\n", &C::c); // output value is 0x4
std::cout << &C::c << std::endl; // output value is 1
// printf("%p\n", &(C::c)); // compile error, invalid use of non-static data member 'C::c'
co.foo();
return 0;
}
- According to C++ operator Precedence,the
::
operator has higher precedence than the&
operator. I think&C::c
is equal to&(C::c)
, but the output says otherwise. Why are they different? -
&(C::c)
causes a compile error in main but not in thefoo
function,why is that? - The value of
&C::c
is different inprintf
andstd::cout
, why is that?
C++ distinguishes two forms of operands to the &
operator, lvalues in general and (qualified) identifiers specifically. In &C::c
the operand of &
is a qualified identifier (i.e. just a name) whereas in &(C::c)
the operand is a general expression (because (
cannot be part of a name).
The qualified identifier form has a special case: If it refers to a non-static member of a class (like your C::c
), &
returns a special value known as a "pointer to member of C". See here for more information about member pointers.
In &(C::c)
there is no special case. C::c
is resolved normally and fails because there is no object to get a c
member of. At least that's what happens in main
; in methods of C
(like your foo
) there is an implicit this
object, so C::c
actually means this->c
there.
As for why the output is different for printf
vs. cout
: When you try to print a member pointer with <<
, it is implicitly converted to a bool
, yielding false
if it's a null pointer and true
otherwise. false
is printed as 0
; true
is printed as 1
. Your member pointer is not null, so you get 1
. This is different from normal pointers, which are implicitly converted to void *
and printed as addresses, but member pointers cannot be converted to void *
so the only applicable overload of operator<<
is the one for bool
. See https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt#Notes.
Note that technically your printf
calls have undefined behavior. %p
takes a void *
and you're passing it pointers of different types. In normal function calls the automatic conversion from T *
to void *
would kick in, but printf
is a variable-arguments function that provides no type context to its argument list, so you need a manual conversion:
printf("%p\n", static_cast<void *>(&(C::c)));
The relevant part of the standard is [expr.unary.op], saying:
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 or variant memberm
of some classC
with typeT
, the result has type “pointer to member of classC
of typeT
” and is a prvalue designatingC::m
. Otherwise, if the type of the expression isT
, the result has type “pointer toT
” [...]
While the expression &C::c
result in a pointer to member c
the expression &(C::c)
yield the address of the member variable c
. The difference you're seeing in the output is that std::cout
involve a bool implicit conversion that tells you whether the pointer is null or not.
Since &C::c
is actually not null is implicitly converted to bool
with value true
or 1
Q1: There is special meaning to the syntax of &
followed by an unparenthesized qualified-id. It means to form a pointer-to-member. Furthermore, there is no other way to form a pointer-to-member. This is covered by C++17 [expr.unary.op]/4:
A pointer to member is only formed when an explicit
&
is used and its operand is a qualified-id not enclosed in parentheses. [Note: That is, the expression&(qualified-id)
, where the qualified-id is enclosed in parentheses, does not form an expression of type “pointer to member”. Neither does qualified-id [...]
Q3: In both cases where you write printf("%p\n", &C::c);
, &C::c
is a pointer-to-member. The %p
format specifier is only for void *
so this causes undefined behaviour, and the program output is meaningless.
The code cout << &C::c;
outputs a pointer-to-member via operator<<(bool val)
, since there is implicit conversion from pointer-to-member to bool
(with result true
in all cases), see [conv.bool]/1.
For further discussion of how to print a pointer-to-member, see this answer.
Q2: The code &(C::c)
does not form a pointer-to-member as explained above.
Now, the code C::c
is in the grammatical category id-expression. (Which is qualified-id and unqualified-id). An id-expression has some restrictions on its use, [expr.prim.id]/1:
An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
- as part of a class member access in which the object expression refers to the member’s class or a class derived from that class, or
- to form a pointer to member (7.6.2.1), or
- if that id-expression denotes a non-static data member and it appears in an unevaluated operand.
When we are inside the C::foo
function, the first of those bullet points applies. The code is the same as &c
but with unnecessary qualification. This has type int *
. You could output this with std::cout << &(C::c);
which would show a memory address, the address of this->c
.
When we are in the main
function , none of the three bullet points apply and therefore the &(C::c)
is ill-formed.