How can I reliably get an object's address when operator& is overloaded?
Use std::addressof
.
You can think of it as doing the following behind the scenes:
- Reinterpret the object as a reference-to-char
- Take the address of that (won’t call the overload)
- Cast the pointer back to a pointer of your type.
Existing implementations (including Boost.Addressof) do exactly that, just taking additional care of const
and volatile
qualification.
Update: in C++11, one may use std::addressof
instead of boost::addressof
.
Let us first copy the code from Boost, minus the compiler work around bits:
template<class T>
struct addr_impl_ref
{
T & v_;
inline addr_impl_ref( T & v ): v_( v ) {}
inline operator T& () const { return v_; }
private:
addr_impl_ref & operator=(const addr_impl_ref &);
};
template<class T>
struct addressof_impl
{
static inline T * f( T & v, long ) {
return reinterpret_cast<T*>(
&const_cast<char&>(reinterpret_cast<const volatile char &>(v)));
}
static inline T * f( T * v, int ) { return v; }
};
template<class T>
T * addressof( T & v ) {
return addressof_impl<T>::f( addr_impl_ref<T>( v ), 0 );
}
What happens if we pass a reference to function ?
Note: addressof
cannot be used with a pointer to function
In C++ if void func();
is declared, then func
is a reference to a function taking no argument and returning no result. This reference to a function can be trivially converted into a pointer to function -- from @Konstantin
: According to 13.3.3.2 both T &
and T *
are indistinguishable for functions. The 1st one is an Identity conversion and the 2nd one is Function-to-Pointer conversion both having "Exact Match" rank (13.3.3.1.1 table 9).
The reference to function pass through addr_impl_ref
, there is an ambiguity in the overload resolution for the choice of f
, which is solved thanks to the dummy argument 0
, which is an int
first and could be promoted to a long
(Integral Conversion).
Thus we simply returns the pointer.
What happens if we pass a type with a conversion operator ?
If the conversion operator yields a T*
then we have an ambiguity: for f(T&,long)
an Integral Promotion is required for the second argument while for f(T*,int)
the conversion operator is called on the first (thanks to @litb)
That's when addr_impl_ref
kicks in. The C++ Standard mandates that a conversion sequence may contain at most one user-defined conversion. By wrapping the type in addr_impl_ref
and forcing the use of a conversion sequence already, we "disable" any conversion operator that the type comes with.
Thus the f(T&,long)
overload is selected (and the Integral Promotion performed).
What happens for any other type ?
Thus the f(T&,long)
overload is selected, because there the type does not match the T*
parameter.
Note: from the remarks in the file regarding Borland compatibility, arrays do not decay to pointers, but are passed by reference.
What happens in this overload ?
We want to avoid applying operator&
to the type, as it may have been overloaded.
The Standard guarantees that reinterpret_cast
may be used for this work (see @Matteo Italia's answer: 5.2.10/10).
Boost adds some niceties with const
and volatile
qualifiers to avoid compiler warnings (and properly use a const_cast
to remove them).
- Cast
T&
tochar const volatile&
- Strip the
const
andvolatile
- Apply the
&
operator to take the address - Cast back to a
T*
The const
/volatile
juggling is a bit of black magic, but it does simplify the work (rather than providing 4 overloads). Note that since T
is unqualified, if we pass a ghost const&
, then T*
is ghost const*
, thus the qualifiers have not really been lost.
EDIT: the pointer overload is used for pointer to functions, I amended the above explanation somewhat. I still do not understand why it is necessary though.
The following ideone output sums this up, somewhat.
The trick behind boost::addressof
and the implementation provided by @Luc Danton relies on the magic of the reinterpret_cast
; the standard explicitly states at §5.2.10 ¶10 that
An lvalue expression of type
T1
can be cast to the type “reference toT2
” if an expression of type “pointer toT1
” can be explicitly converted to the type “pointer toT2
” using areinterpret_cast
. That is, a reference castreinterpret_cast<T&>(x)
has the same effect as the conversion*reinterpret_cast<T*>(&x)
with the built-in&
and*
operators. The result is an lvalue that refers to the same object as the source lvalue, but with a different type.
Now, this allows us to convert an arbitrary object reference to a char &
(with a cv qualification if the reference is cv-qualified), because any pointer can be converted to a (possibly cv-qualified) char *
. Now that we have a char &
, the operator overloading on the object is no longer relevant, and we can obtain the address with the builtin &
operator.
The boost implementation adds a few steps to work with cv-qualified objects: the first reinterpret_cast
is done to const volatile char &
, otherwise a plain char &
cast wouldn't work for const
and/or volatile
references (reinterpret_cast
cannot remove const
). Then the const
and volatile
is removed with const_cast
, the address is taken with &
, and a final reinterpet_cast
to the "correct" type is done.
The const_cast
is needed to remove the const
/volatile
that could have been added to non-const/volatile references, but it does not "harm" what was a const
/volatile
reference in first place, because the final reinterpret_cast
will re-add the cv-qualification if it was there in first place (reinterpret_cast
cannot remove the const
but can add it).
As for the rest of the code in addressof.hpp
, it seems that most of it is for workarounds. The static inline T * f( T * v, int )
seems to be needed only for the Borland compiler, but its presence introduces the need for addr_impl_ref
, otherwise pointer types would be caught by this second overload.
Edit: the various overloads have a different function, see @Matthieu M. excellent answer.
Well, I'm no longer sure of this either; I should further investigate that code, but now I'm cooking dinner :) , I'll have a look at it later.