Всем программистам C++ хорошо известно, как получить адрес области памяти, где хранится значение переменной - нужно задействовать оператор взятия адреса (&). Оператор этот унарный. При этом есть и бинарный оператор &, который реализует операцию побитового И. Но нас он совсем не интересует. 

Также, программистам C++ хорошо известно и то, что операторы можно перегружать. И, наверное, хотя бы раз в жизни все пробовали перегрузить для своего собственного типа какой-нибудь оператор (например, [ ]), в надежде, что это упростит работу с данным типом.

Однако не все операторы могут быть перегружены. Например, оператор .  или :: не могут быть перегружены, да и сложно себе представить, чтобы из этого вышло. Представьте, Вы вызвали sizeof для типа T,  и получили в результате 0, потому что кому-то так захотелось. А ведь результат от вызова  sizeof очень часто используется в знаменателе, и никогда не должен быть равен 0.

Но унарный оператор &, оказывается можно перегрузить! Зачем это нужно, кто так делает и как же в этом случае "взять адрес" читайте далее.

Неприятная перегрузка

Приведем код, который лишен практического смысла, но абсолютно "законен" для C++. Объявим класс Foo, у которого перегрузим унарный оператор &

Если теперь попытаться узнать адрес объекта типа Foo, то мы всегда будем получать результат - 0.

Возникают два вопроса:

  1. когда может понадобиться делать перегрузку такого незыблемого  оператора как &;
  2. и как все таки получить адрес объекта, в случае такой перегрузки. 

В интернете есть упоминания о том, как можно реализовать классы-обертки (wrapper), которые как раз задействуют перегрузку оператора &. Не станем давать ссылки на столь вредный материал, но общая идея примерно такая:

Такие перегрузки встречаются в библиотеке ATL (Active Template Library), которая разрабатывалась компанией Microsoft. Вот перечень классов-оберток, перегружающих оператор &: CComTypeAttr, CComVarDesc, CComFuncDesc, CComPtr и так далее. Вот Вам и применение на практике...

Рассмотрим теперь частично реализацию контейнера std::vector из стандартной библиотеки STL. Реализацию вектора возьмем от всё той же компании Microsoft. Вот как выглядит метод push_back:

Объект, который будет добавлен в контейнер передается по ссылке _Val. Вызывается метод _Inside, который проверяет, "а не содержится ли уже этот объект в контейнере". Метод _Inside выполняет проверку очень просто - путем сравнения указателей: 

Из всего этого Вы должны понять, что стандартная библиотека всегда должна иметь возможность узнать адрес какого-либо объекта. Даже если оператор & будет перегружен у типа. Но как узнать адрес без &.

Решение

Чтобы явно не вызывать & разработчики библиотек прибегали вот к такой жуткой конструкции:

Шаблонная функция addressof была также реализована в библиотеке boost. А начиная с нового стандарта C++11 addressof "переместился" в стандартную библиотеку. У Microsoft она реализована так (кстати, также она реализована и в GCC):

Вернемся к нашему первому примеру с классом Foo, и все-таки получим адрес объекта foo:

std::addressof применяется в STL очень часто, и на то есть причины. Любая библиотека общего назначения, предназначеная для широкого использования, не должна использовать оператор & для получения адреса объектов неизвестного (на этапе компиляции) типа. Для этого нужно использовать std::addressof. Иначе, на такую библиотеку будут наложены ограничения. Ни для boost, ни для stl такие ограничения неприемлемы. Если Вы - разработчик библиотеки, Вам стоит обратить на это внимание. Если же, Вы не разрабатываете библиотек, то Вам стоит "забыть на всегда" о том, что унарный оператор &, может быть перегружен.


Напоследок заметим, что в реализации addressof присутствует reinterpret_cast (даже дважды). Про reinterpret_cast обычно пишут так:

Не портируемо, результат может быть некорректным, никаких проверок не делается.

Но такое приведение типов присутствует в каждой реализации STL, и это обычно не вызывает проблем.