Ответ 1
Обновление: в С++ 11, вместо boost::addressof
можно использовать std::addressof
.
Скопируем сначала код из Boost, за исключением того, что компилятор работает с битами:
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 );
}
Что произойдет, если мы передадим ссылку на функцию?
Примечание: addressof
не может использоваться с указателем на функцию
В С++, если объявлено void func();
, тогда func
является ссылкой на функцию, не принимающую никаких аргументов и не возвращающую результат. Эта ссылка на функцию может быть тривиально преобразована в указатель на функцию - от @Konstantin
: согласно 13.3.3.2 оба T &
и T *
неотличимы для функций. Первый - это преобразование Идентичности, а второе - преобразование из функции в указатель, имеющее ранг "Точное совпадение" (таблица 13.3.3.1.1, таблица 9).
Ссылка на функцию проходит через addr_impl_ref
, существует двусмысленность в разрешении перегрузки для выбора f
, которое решается благодаря фиктивному аргументу 0
, который является int
первым и может повышается до long
(интегральное преобразование).
Таким образом, мы просто возвращаем указатель.
Что произойдет, если мы передадим тип с оператором преобразования?
Если оператор преобразования дает T*
, тогда мы имеем двусмысленность: для f(T&,long)
для второго аргумента требуется интегральное продвижение, а для f(T*,int)
оператор преобразования вызывается на первом (благодаря @litb)
Это, когда addr_impl_ref
запускается. Стандарт С++ требует, чтобы последовательность преобразования могла содержать не более одного определяемого пользователем преобразования. Упаковывая тип в addr_impl_ref
и заставляя использовать последовательность преобразования уже, мы "отключим" любой оператор преобразования, к которому относится тип.
Таким образом, выбирается перегрузка f(T&,long)
(и выполняется интегральное продвижение).
Что происходит для любого другого типа?
Таким образом, выбрана перегрузка f(T&,long)
, потому что тип не соответствует параметру T*
.
Примечание: из замечаний в файле, касающихся совместимости Borland, массивы не распадаются на указатели, а передаются по ссылке.
Что происходит в этой перегрузке?
Мы хотим избежать применения operator&
к типу, поскольку он, возможно, был перегружен.
Стандарт гарантирует, что reinterpret_cast
может использоваться для этой работы (см. ответ @Matteo Italia: 5.2.10/10).
Boost добавляет некоторые тонкости с квалификаторами const
и volatile
, чтобы избежать предупреждений компилятора (и правильно использовать const_cast
для их удаления).
- Вставить
T&
вchar const volatile&
- Разделите
const
иvolatile
- Примените оператор
&
, чтобы принять адрес - Вернитесь к
T*
Жонглирование const
/volatile
немного черной магии, но оно упрощает работу (а не обеспечивает 4 перегрузки). Заметим, что поскольку T
является неквалифицированным, если мы передаем a ghost const&
, то T*
является ghost const*
, поэтому квалификаторы действительно не потеряны.
EDIT: перегрузка указателя используется для указателя на функции, я немного изменил приведенное выше описание. Я все еще не понимаю, зачем это нужно.
Следующий ideone output немного подводит итог.