Ответ 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;