茂加部珈琲店

主にtech関連のメモ置き場です

constexprでメンバ変数のオフセットを取得する

constexprでも頑張ればメンバオフセットを取得できるみたいです。constexproffsetofですね
コードはこのGithubでの議論あたりを参考にしました。

コンパイルにはc++17が必要です
gcc 7.0 / clang 6.0 あたりでコンパイルできます。
msvcVS2017 15.8 Preview 3からコンパイルできようになりました。やったね!

  template <typename T1, typename T2>
  struct offset_of_member_impl {
    union U {
      U() : c{} {}
      ~U() {}
      char c[sizeof(T2)];
      T2 o;
    };
    static U u;
    static constexpr size_t get(T1 T2::*member) {
      size_t i = 0;
      for (; i < sizeof(T2); ++i)
        if (((void*)&(u.c[i])) == &(u.o.*member)) break;

      // g++ bug 67371 workaround
      if (i >= sizeof(T2))
        throw std::runtime_error("failed to detect offset");
      else
        return i;
    }
  };

  // suppress warning
  template <class T1, class T2>
  typename offset_of_member_impl<T1, T2>::U offset_of_member_impl<T1, T2>::u{};

  /// get offset of member
  template <class T1, class T2>
  constexpr size_t offset_of_member(T1 T2::*member) {
    return offset_of_member_impl<T1, T2>::get(member);
  }

g++のバグを回避するためにループをばらしています。
こんな感じで使います

struct A {
  int i;
  char c;
  double d;
};

int main() {
  constexpr size_t offi = offset_of_member(&A::i);
  constexpr size_t offc = offset_of_member(&A::c);
  constexpr size_t offd = offset_of_member(&A::d);
  
  std::cout << offi << std::endl; //0
  std::cout << offc << std::endl; //4
  std::cout << offd << std::endl; //8
}