Jak wspomniano w komentarzach, można wykryć, czy przeciążony operator&
jest dostępna za pomocą SFINAE. I jak Potatoswatter zwraca uwagę w komentarzach, to trzeba mieć trzy oddzielne kontrole:
1) czy x.operator&()
akceptuje
2) czy operator&(x)
akceptuje
Pierwsze dwa są dwa sposoby użytkownik -provided operator&
może być zdefiniowany.
3) czy &x
akceptuje
Ta trzecia kontrola jest konieczna, ponieważ x.operator&()
może zostać odrzucony, ponieważ operator&
istnieje, ale jest prywatny. W takim przypadku &x
jest nieprawidłowe.
Te kontrole można wprowadzić, sprawdzając sizeof(f(std::declval<T>()))
, gdzie f
jest przeciążone w taki sposób, że typ zwracany zależy od tego, czy T
przejdzie kontrolę.
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];
}
Następnie można korzystać z tych funkcji pomocniczych na wybór realizacja addressof
używać:
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)));
}
Pozwala addressof
być stosowane w stałych wyrażeniach tak długo, jak operator&
nie jest przeciążony.Jeśli jest przeciążony, wydaje się, że nie ma sposobu, aby niezawodnie uzyskać adres w formie, która jest użyteczna w wyrażeniu stałym.
Należy pamiętać, że GCC 4.7 odrzuca zastosowania tych przypadków implementacji addressof
, w których powinien działać. GCC 4.8 i wyższe działają, podobnie jak klang.
Użyłem pojedynczej implementacji addressof
, która została przekierowana do funkcji pomocniczej we wcześniejszej wersji mojej odpowiedzi, ale niedawno uświadomiłem sobie, że to nie jest dobry pomysł, ponieważ może łatwo doprowadzić do naruszeń ODR, jeśli addressof<X>
jest stosowany w niektórych jednostkach klasy X
w wielu jednostkach tłumaczeniowych, w niektórych z nich zdefiniowano X
, a w niektórych z nich X
jest niekompletny. Posiadanie dwóch oddzielnych funkcji pozwala uniknąć tego problemu.
Jedynym pozostałym problemem jest to, że może zawieść jeśli addressof<X>
jest używane w jednostce tłumaczeniowej przed definicją X
niestandardowego operator&
. Powinno to być na tyle rzadkie, że nie stanowi to problemu w praktyce.
testami dla sensownych przykładów:
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;
Mogę zapytać dlaczego jest to potrzebne? –
Adresy obiektów nie są znane podczas kompilacji. – Erik
To nie jest możliwe: rzuty dziwne nie mogą być "constexpr". Ale nie mogę sobie wyobrazić sytuacji, w której potrzebny byłby "adres constexpr". –