17

関数の constexpr アドレスを記述する必要がありますが、それは不可能です。これが可能かどうか誰にもわかりますか?

cppreference.comの参照実装:

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

(GCC 実装と同様に) reinterpret_cast を使用するため、実行されません。最新の C++ 標準ドラフトであるN3485では、ヘッダー <utility> からの多くの関数が最近 constexpr にアップグレードされたにもかかわらず、addressof() が constexpr であることを要求していないことがわかります。

あまり説得力や有用性はありませんが、可能性のあるユースケースは次のとおりです。

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

MyType が operator& をオーバーロードしたとします。

4

2 に答える 2

9

コメントで述べたように、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>XXX

唯一残っている問題は、 がの custom のaddressof<X>定義の前に翻訳単位で使用された場合に失敗する可能性があることです。これは、実際には問題にならないほどまれであることを願っています。Xoperator&

賢明な例のテストケース:

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;
于 2013-02-14T19:10:22.940 に答える
1

部分的な回避策の1つは、ラッパー内でそのようなオブジェクトを定義し、unionポインターをに渡すことunionです。ラッパーへのポインターは、型への参照に簡単に変換できます。ポインタ演算は、ラッパーの配列で機能するはずです。しかし、オーバーロードされた型へのポインターを取得する方法はまだわかりませんoperator&

union必要なメンバーは1人だけです。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
于 2013-02-14T08:20:03.027 に答える