茂加部珈琲店

主に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;
      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);
  }

こんな感じで使います

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
}

VSCodeでMSYS2+fishを使う

settings.jsonに以下を追加すればよい

    "terminal.integrated.shell.windows": "C:\\Windows\\System32\\cmd.exe",
    "terminal.integrated.shellArgs.windows": [
        "/K","C:\\msys64\\msys2_shell.cmd -mingw64 -defterm -no-start -here -full-path -c fish & exit 0"
    ]

ターミナル起動時にfishを呼んで、fishが終了したときにターミナルも終了させます
なんか時々表示が崩れたり謎の改行が入ったりする

追記:
改行文字(\u23CE)が表示されるのはWindowsのバグなのでしょうかね

github.com

参照: msys2_shell.cmdを使ってVS Codeの総合シェルをMSYS2 bashにする

フレキシブル配列メンバをC++で

こんにちは。c++ネタ。

flexible array member

プログラミングをしていると、大きさのわからない配列を扱いたい時があります。
間接参照を挟まず、構造体に直接配列を埋め込む手法はCなどではよく使われています
こういった配列を扱うため、C99からはフレキシブル配列flexible array memberという機能が追加されました。 使い方は以下のようになります

struct flexArrayStruct {
  int num;
  int data[];
};

末尾のdata[]がフレキシブル配列で、要素数の不明な配列を表します。プログラムは実行時に任意の長さのペイロードを含むオブジェクトをメモリ上に確保してアクセスすることができます。

フレキシブル配列の実体は大雑把に言えば大きさ0の配列です。つまり、事前に「次に来る要素」にアクセスする手段を提供する手段ということです。

C++でフレキシブル配列メンバを模倣する その1

本題に入りますが、C++はC99ベースに作られていないので、フレキシブル配列を使用することはできません。
いくつかのコンパイラは独自拡張としてフレキシブル配列をサポートしています

struct flexArrayStruct {
    int num;
    int data[]; // gcc/clangでコンパイル可能
};

しかし、フレキシブル配列を持つクラスは継承することができません。

  struct Derived : flexArrayStruct {}; // error: base class 'flexArrayStruct' has a flexible array member

これは異論がある人もいると思いますが、フレキシブル配列を持つ構造体を継承すれば、ポインタがキャスト可能になり若干嬉しいです。
そこで、フレキシブル配列ではなく、長さゼロの配列を使用します

// gcc/clangでコンパイル可能
struct flexArrayStruct {
    int num;
    int data[0] = {};
};

struct Derived : flexArrayStruct {
    Derived() :flexArrayStruct{8} {}
    int data[8] = {1,2,3,4,5,6,7,8};
};

flexArrayStruct::dataDerived::dataはメモリ上で同一の場所を表します。
C++の規格上は、長さゼロの配列も、固有のアドレスを持たないオブジェクトも違法です。
そもそも配列の要素数を超えたアクセスはUBですが、耳から天使を召喚して何とかしてください
(ちなみにstd::array<0>は許されていますが、オブジェクトのサイズはゼロではないので注意) また、clang/gccではコンパイルできますが、ゼロ長配列を持つ基底クラスを許さないmsvcではコンパイルできません。

C++でフレキシブル配列メンバを模倣する その2

MSVCでもコンパイルしたい場合は上記の手法は使えませんでした。
こういうときは何も考えずにテンプレートを使ってみるのがC++

template <int N=1>
struct A : A<N-1> {
    int _data_element= {};
};
template <>
struct A<1> {
    int num;
    int data[1] = {};
};
struct D : A<8> {
  D() :A<8>{8,1,2,3,4,5,6,7,8} {}
};

うーん… 非常に危険な香りがしますね

いずれにせよ、C++でフレキシブル配列を行儀よくやるのは無理そうというのが感想です。 より行儀が良い手法としてはポインタ演算でゴリゴリやるしかなさそうです。 フレキシブル配列メンバが規格に入れば良いんですが、忘れられてしまったんでしょうか

[g++7.1] ジェネリックラムダが不要な場合にもインスタンス化されるバグ

ちょっと調べたのでメモ
以下のコードはg++ 7.1、g++7.2でコンパイルに失敗します。

#include <variant>
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

using v = std::variant<double, long, bool>;
 
constexpr const char* get_name(const v& v) {
   return std::visit(
    overloaded{
      [](bool) constexpr { return "bool"; },
      [](double) constexpr { return "double"; },
      [](long) constexpr { return "long"; }, 
      // 網羅していない場合はコンパイルを失敗させる
      [](auto a) constexpr { static_assert(!sizeof(decltype(a)));  }},
    v);
}

これはジェネリックラムダが不要な場合にもインスタンス化されるためです。
以前から報告されており、g++7.3で修正されました。

メンバ関数テンプレートでC2783が出る場合

小ネタなのですが VC++(VS2017 15.5.2)で以下のコードはコンパイルに失敗します。
clangとgccでは通ります。

#include <type_traits>

struct Foo {
  template <class U = int, class = std::enable_if_t<std::is_same_v<U,int>>>
  void f();
};

//template <class U, class> // <- Ok.
template <class, class> // <- C2783: could not deduce template argument for '<unnamed-symbol>'
void Foo::f(){}

int main()
{
  Foo f;
  f.f();
}

エラメッセージでは宣言を確認してくださいなどと書かれていますが、問題はコメントアウトしてあるテンプレートパラメータのようです。
型名までしっかり一致させないといけないので注意。

if constexprを失敗させる

if constexprを与えられた型によって失敗させる場合は、
型に依存したパラメータをstatic_assertに渡してやれば良いみたいです。

saka1_pさんの記事によれば、

template <typename T>
constexpr bool false_v = false;

のような定数を定義して、

  // if constexprの内部で
  static_assert(false_v<T>, "invalid type T");

のようにするらしいです。

個人的には

  static_assert(!sizeof(T), "invalid type T");

が好みです。

VSCodeでcmakeを走らせる

コマンド一つでcmakeやctestを走らせたかったのです。

VSCodeではtask.jsonを使って簡単にできるみたいです.
WindowsではデフォルトのコンパイラVC++なので、MinGWを指定してあります
mingw-64でも動きました。

Ctrl+Shift+B で走ります

{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "presentation": {
    "echo": true,
    "reveal": "always",
    "focus": false,
    "panel": "new"
  },
  "tasks": [
    {
      "label": "build",
      "type": "shell",
      "windows": {
        "command": "",
        "args": [
          "mkdir build; cd build; cmake -G 'MinGW Makefiles' ..; cmake --build .; ctest -V -C Debug"
        ]
       },
      "linux": {
        "command": "sh",
        "args": [
          "mkdir build; cd build; cmake ..; cmake --build .; ctest -V"
        ]
       },
       "problemMatcher": []
    }
  ]
}