Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementation of addressof

Tags:

c++

c++11

Having previously been unaware of the existence of std::addressof, why it exists makes sense to me: as a way of taking the an address in the presence of an overloaded operator&. The implementation, however, is slightly more opaque. From gcc 4.7.1:

template<typename _Tp>
inline _Tp*
__addressof(_Tp& __r) _GLIBCXX_NOEXCEPT
{
  return reinterpret_cast<_Tp*>
(&const_cast<char&>(reinterpret_cast<const volatile char&>(__r)));
}

The reinterpret_cast<_Tp*> is obvious. The rest of it is dark magic. Can someone break down how this actually works?

like image 941
Yuushi Avatar asked Apr 24 '13 14:04

Yuushi


2 Answers

  • First you have __r which is of type _Tp&
  • It is reinterpret_cast'ed to a char& in order to ensure being able to later take its address without fearing an overloaded operator& in the original type; actually it is cast to const volatile char& because reinterpret_cast can always legally add const and volatile qualifiers even if they are not present, but it can't remove them if they are present (this ensures that whatever qualifiers _Tp had originally, they don't interfere with the cast).
  • This is const_cast'ed to just char&, removing the qualifiers (legally now! const_cast can do what reinterpret_cast couldn't with respect to the qualifiers).
  • The address is taken & (now we have a plain char*)
  • It is reinterpret_cast'ed back to _Tp* (which includes the original const and volatile qualifiers if any).

Edit: since my answer has been accepted, I'll be thorough and add that the choice of char as an intermediate type is due to alignment issues in order to avoid triggering Undefined Behaviour. See @JamesKanze's comments (under the question) for a full explanation. Thanks James for explaining it so clearly.

like image 174
syam Avatar answered Nov 17 '22 18:11

syam


It's actually quite simple when you think about it, to get the real adress of an object/function in precense of an overloaded operator& you will need to treat the object as something other than what it really is, some type which cannot have an overloaded operator.. an intrinsic type (such as char).

A char has no alignment and can reside anywhere any other object can, with that said; casting an object to a reference to char is a very good start.


But what about the black magic involved when doing reinterpret_cast<const volatile char&>?

In order to reinterpret the returned pointer from the implementation of addressof we will eventually want to discard qualifiers such as const and volatile (to end up with a plain reference char). These two can be added easily with reinterpret_cast, but asking it to remove them is illegal.

T1 const a; reinterpret_cast<T2&> (a);

/* error: reinterpret_cast from type ‘...’ to type ‘...’ casts away qualifiers */

It's a little bit of a "better safe than sorry" trick.. "Let us add them, just in case, we will remove them later."


Later we cast away the qualifiers (const and volatile) with const_cast<char&> to end up with a plain reference to char, this result is, as the final step, turned back into a pointer to whatever type we passed into our implementation.

A relevant question on this stage is why we didn't skip the use of reinterpret_cast and went directly to the const_cast? this too has a simple answer: const_cast can add/remove qualifiers, but it cannot change the underlying type.

T1 a; const_cast<T2&> (a);

/* error: invalid const_cast from type ‘T1*’ to type ‘T2*’ */

it might not be easy as pie, but it sure tastes good when you get it..

like image 13
Filip Roséen - refp Avatar answered Nov 17 '22 16:11

Filip Roséen - refp