C++で構造体の名前付き初期化
C言語のライブラリを使っていると、構造体をデフォルト初期化してから、一部のメンバー変数だけ書き換えて利用することがしばしばあります。
option o = {}; o.name = "hello"; o.size = 1234; func(o);
option 構造体のメンバー変数の仕様は一部だけが決まっていて、他のメンバー変数は実装詳細として隠されていたりします。
また、一部のメンバー変数だけ値を設定したい場合があります。
このような構造体の値を式の中でインラインで書く方法を考えてみました。
C++ではこうすれば良さそうです。
func([]{ option o = {}; o.name = "hello"; o.size = 1234; return o; }());
ただ、タイプ量が増えている上に、唐突な return の「おまじない感」がぬぐえません。
マクロで抽象化してみます。
func(MAKE_STRUCT( option, name = "hello", size = 1234 )); func(MAKE_STRUCT( option, name = "abc" )); func(MAKE_STRUCT( option, size = 42 ));
こんな風に書ければ良いと思います。余計な記号も変数名も要らないのでスッキリしました。
あとはこのマクロを実装するだけです。
ついでなので、古いC++でも使えるように考えてみました。たぶん、同じマクロがこんな感じに展開されれば良さそうです。
func(make_struct<option>(( named_arg / &option::name = "hoge", named_arg / &option::size = 1234)));
古いC++ではラムダ式を使えないので、演算子オーバーロードを使って名前付き引数のようなものを作って、なんとかする作戦です。
#define PP_CAT(a,b) PP_CAT_(a,b) #define PP_CAT_(a,b) a##b #define PP_SIZE(...) PP_SIZE_(__VA_ARGS__,10,9,8,7,6,5,4,3,2,1) #define PP_SIZE_(a,b,c,d,e,f,g,h,i,j,k,...) k #define PP_J(x,y,...) PP_CAT(PP_J,PP_SIZE(_,##__VA_ARGS__))(x,y,##__VA_ARGS__) #define PP_J1(x,y) #define PP_J2(x,y,a) x(y,a) #define PP_J3(x,y,a,...) x(y,a) PP_J2(x,y,__VA_ARGS__) #define PP_J4(x,y,a,...) x(y,a) PP_J3(x,y,__VA_ARGS__) #define PP_J5(x,y,a,...) x(y,a) PP_J4(x,y,__VA_ARGS__) #define PP_J6(x,y,a,...) x(y,a) PP_J5(x,y,__VA_ARGS__) #define PP_J7(x,y,a,...) x(y,a) PP_J6(x,y,__VA_ARGS__) #define PP_J8(x,y,a,...) x(y,a) PP_J7(x,y,__VA_ARGS__) #define PP_J9(x,y,a,...) x(y,a) PP_J8(x,y,__VA_ARGS__) #define PP_J10(x,y,a,...) x(y,a) PP_J9(x,y,__VA_ARGS__) #define MAKE_STRUCT(type,...) \ MAKE_STRUCT_1(type,PP_J(MAKE_STRUCT_2,type,##__VA_ARGS__)) #if __cplusplus > 201103L #define MAKE_STRUCT_1(type,m) []{ type x = {}; (void)0 m; return x; }() #define MAKE_STRUCT_2(type,member) ,x.member #else #define MAKE_STRUCT_1(type,m) ts::make_struct<type>((ts::no_assign m)) #define MAKE_STRUCT_2(type,member) ,ts::named_arg/&type::member namespace ts { template <typename T,typename M> T make_struct(M m) { T x = {}; m.apply(x); return x; } struct no_assign_t { template <typename T> void apply(T&) const {} template <typename X> X operator ,(X x) const { return x; } }; const no_assign_t no_assign = {}; template <typename A,typename B> struct assign2 { A a; B b; template <typename T> void apply(T& x) const { a.apply(x); b.apply(x); } }; template <typename T,typename M,typename V> struct assign1 { M (T::*mp); const V& v; void apply(T& x) const { x.*mp = v; } template <typename X> assign2<assign1,X> operator ,(X x) const { assign2<assign1,X> a = { *this, x }; return a; } }; template <typename T,typename M> struct key { M (T::*mp); template <typename V> assign1<T,M,V> operator =(const V& v) const { assign1<T,M,V> m = { mp, v }; return m; } }; struct named_arg_t { template <typename T,typename M> key<T,M> operator /(M (T::*mp)) const { key<T,M> m = { mp }; return m; } }; const named_arg_t named_arg = {}; } //namespace ts #endif
マクロなので引数の数に上限があって、とりあえず9個までという貧乏仕様です。
冒頭に書いた目的に限れば、割と使えるのではないでしょうか?
ちなみに以下のブログ記事から着想を得ました。
http://d.hatena.ne.jp/osyo-manga/20120202/1328108867
追記(2014/06/13)
なんと、親切な人が添削してくれました。
勉強になりました。
ちなみに、__VA_ARGS__の前の##はGCC拡張です。ばれた。
MAKE_STRUCT( type ) って書いても動くようにする悪あがきでした。