Может ли addressof() быть реализован как функция constexpr? - программирование
Подтвердить что ты не робот

Может ли addressof() быть реализован как функция constexpr?

Мне нужно написать функцию constexpr addressof, но я считаю это невозможным. Кто-нибудь знает, возможно ли это?

Эталонная реализация в cppreference.com:

template< class T >
T* addressof(T& arg) 
{
  return reinterpret_cast<T*>(
           &const_cast<char&>(
              reinterpret_cast<const volatile char&>(arg)
           )
         );
}

использует reinterpret_cast (аналогично реализации GCC), поэтому он не будет делать. Я вижу, что последний проект С++ Standard, N3485 также не требует, чтобы addressof() был constexpr, хотя множество функций формировали заголовок < утилита > недавно были обновлены до constexpr.

Возможно, хотя и не очень убедительный или полезный прецедент для него:

constexpr MyType m;
constexpr MyType const* p = &m;           // this works today
constexpr MyType const* p = addressof(m); // this is my question

Представьте, что MyType перегрузил оператор &.

4b9b3361

Ответ 1

Как уже упоминалось в комментариях, вы можете определить, доступен ли перегруженный operator& с помощью SFINAE. И, как указывает Potatoswatter в комментариях, это должны быть три отдельных проверки:

1) принимается ли x.operator&()

2) принимается ли operator&(x)

Первые два - это два способа, которым может быть предоставлен пользовательский operator&.

3) принимается ли &x

Эта третья проверка необходима, потому что x.operator&() может быть отклонен, потому что operator& существует, но является закрытым. В этом случае &x недействителен.

Эти проверки могут быть реализованы путем проверки sizeof(f(std::declval<T>())), где f перегружается таким образом, что тип возвращаемого значения зависит от того, проходит ли проверка T.

namespace addressof_helper {
  template <typename T>
  static char (&checkaddressof(...))[1];

  template <typename T>
  static char (&checkaddressof(T &&, typename std::remove_reference<decltype(&std::declval<T &>())>::type * = 0))[2];

  template <typename T>
  static char (&checknonmember(...))[1];

  template <typename T>
  static char (&checknonmember(T &&, typename std::remove_reference<decltype(operator&(std::declval<T &>()))>::type * = 0))[2];

  template <typename T>
  static char (&checkmember(...))[1];

  template <typename T>
  static char (&checkmember(T &&, typename std::remove_reference<decltype(std::declval<T &>().operator&())>::type * = 0))[2];
}

Затем вы можете использовать эти вспомогательные функции, чтобы выбрать, какую реализацию addressof использовать:

template <typename T>
constexpr typename std::enable_if<
  sizeof(addressof_helper::checkaddressof<T>(std::declval<T>())) == 2
  && sizeof(addressof_helper::checknonmember<T>(std::declval<T>())) == 1
  && sizeof(addressof_helper::checkmember<T>(std::declval<T>())) == 1,
  T *>::type addressof(T &t) {
  return &t;
}

template <typename T>
/* no constexpr */ typename std::enable_if<
  sizeof(addressof_helper::checkaddressof<T>(std::declval<T>())) == 1
  || sizeof(addressof_helper::checknonmember<T>(std::declval<T>())) == 2
  || sizeof(addressof_helper::checkmember<T>(std::declval<T>())) == 2,
  T *>::type addressof(T &t) {
  return reinterpret_cast<T *>(&const_cast<char &>(reinterpret_cast<const volatile char &>(t)));
}

Это позволяет использовать addressof в постоянных выражениях, пока operator& не перегружен. Если он перегружен, похоже, нет способа надежно получить адрес в форме, которая может использоваться в постоянном выражении.

Обратите внимание, что GCC 4.7 отклоняет использование этих случаев реализации addressof, где он должен работать. GCC 4.8 и выше работают, как и clang.

Я использовал единственную реализацию addressof, которая была передана вспомогательной функции в более ранней версии моего ответа, но недавно мне стало известно, что это не очень хорошая идея, поскольку она может легко привести к нарушениям ODR, если addressof<X> используется для некоторого класса X в нескольких единицах перевода, в некоторых из которых X определен, а в некоторых из которых X является неполным. Наличие двух отдельных функций позволяет избежать этой проблемы.

Единственная оставшаяся проблема состоит в том, что она может выйти из строя, если addressof<X> используется в блоке трансляции до определения X custom operator&. Это, надеюсь, будет достаточно редким, чтобы на практике это не проблема.

Тесты для разумных примеров:

class A { } a;
class B { private: B *operator&(); } b;
class C { C *operator&(); } c;
class D { } d;
D *operator&(D &);
extern class E e;

int main() {
  constexpr A *pa = addressof(a);
  /* no constexpr */ B *pb = addressof(b);
  /* no constexpr */ C *pc = addressof(c);
  /* no constexpr */ D *pd = addressof(d);
  constexpr E *pe = addressof(e);
}

class E { } e;

Ответ 2

Один частичный обходной путь заключается в том, чтобы определить любой такой объект в оболочке union и передать указатели на union. Указатель на обертку можно легко преобразовать в ссылку на тип. Арифметика указателя должна работать с массивами оберток. Но я до сих пор не вижу способа получить указатель на тип с перегруженным operator&.

union нужен только один элемент; struct будет работать на практике, но теоретически реализация может добавить дополнение в начале. Не то, чтобы отступы действительно имеют значение, если вы все равно не можете получить MyType * в постоянном выражении.

template< typename t >
union storage_wrapper
    { t obj; };

constexpr storage_wrapper< MyType > m{{ args_to_MyType_constructor }};
constexpr storage_wrapper< MyType > *p = &m; // not overloaded