コメントで述べたように、operator&
SFINAE を使用してオーバーロードが利用可能かどうかを検出できます。Potatoswatter がコメントで指摘しているように、これらは 3 つの個別のチェックである必要があります。
1)x.operator&()
受け入れられるかどうか
2)operator&(x)
受け入れられるかどうか
最初の 2 つは、ユーザー提供operator&
を定義する 2 つの方法です。
3)&x
受け入れられるかどうか
この 3 番目のチェックは、存在する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 違反に簡単につながる可能性があるため、これは良い考えではないことに最近気づきました。翻訳単位の一部は定義されており、一部は不完全です。2 つの別個の機能を持つことで、この問題を回避できます。addressof<X>
X
X
X
唯一残っている問題は、 がの custom のaddressof<X>
定義の前に翻訳単位で使用された場合に失敗する可能性があることです。これは、実際には問題にならないほどまれであることを願っています。X
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;