C++ その文字列テーブルはもう古い
その文字列テーブルは動的?静的?
今次のような文字列テーブルがあるとしましょう
foo.inl
DEFINE_FOO( foo, "Foo", "あああ" ) DEFINE_FOO( bar, "Bar", "いいい" ) DEFINE_FOO( baz, "Baz", "ううう" ) DEFINE_FOO( qux, "Qux", "えええ" )
foo.h
enum eFoo { #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) eFoo_##id, #include "foo.inl" #undef DEFINE_FOO elementsof_eFoo }; const char* get_foo_text1( eFoo foo ); const char* get_foo_text2( eFoo foo ); #define FOO_TEXT1( foo ) get_foo_text1( eFoo_##foo ) #define FOO_TEXT2( foo ) get_foo_text2( eFoo_##foo )
foo.cpp
namespace { const char* const c_foo_text1[] = { #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) text1, #include "foo.inl" #undef DEFINE_FOO }; const char* const c_foo_text2[] = { #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) text2, #include "foo.inl" #undef DEFINE_FOO }; } const char* get_foo_text1( eFoo foo ){ assert( foo < elementsof_eFoo ); return c_foo_text1[foo]; } const char* get_foo_text2( eFoo foo ){ assert( foo < elementsof_eFoo ); return c_foo_text2[foo]; }
使用例
debug_printf( "foo = '%s'\n", FOO_TEXT1( baz ));
enum型とそれに関連付けられたテキストがあります
これが動的に解決されるべきものであるならこれで問題はありません
動的というのは実行時ということですが、その実行時に解決されるべきものというのは enumの値がファイルに保存されるものであったり、ネットワークから取得するものであったり、ユーザーが入力するものであったり、そういったもののことです
一方静的というのはコンパイル時ということですが、そのコンパイル時に解決されるべきものというのは、ソースコード中で指定されているもののことです
上記の使用例ではソースコード中で識別子を指定していますね
こういった使い方は静的に解決されるべきものです
静的に解決されるべきものであるならこういった文字列テーブルはもう古いのです
現代的な静的な解決方法
C++ でコンパイル時の問題解決に使うものと言えばもちろんテンプレートです
上記にあるような文字列テーブルはテンプレートに置き換えることができます
foo.h
#undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) struct tagFoo_##id{}; extern tagFoo_##id eFoo_##id; #include "foo.inl" #undef DEFINE_FOO template<typename eFoo> struct get_foo_text1_t; #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) template<> struct get_foo_text1_t<tagFoo_##id> { static const char* get_foo_text1(); }; #include "foo.inl" #undef DEFINE_FOO template<typename eFoo> struct get_foo_text2_t; #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) template<> struct get_foo_text2_t<tagFoo_##id> { static const char* get_foo_text2(); }; #include "foo.inl" #undef DEFINE_FOO template<typename eFoo> const char* get_foo_text1(eFoo){ return get_foo_text1_t<eFoo>::get_foo_text1(); } template<typename eFoo> const char* get_foo_text2(eFoo){ return get_foo_text2_t<eFoo>::get_foo_text2(); } #define FOO_TEXT1( foo ) get_foo_text1( eFoo_##foo ) #define FOO_TEXT2( foo ) get_foo_text2( eFoo_##foo )
foo.cpp
#undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) tagFoo_##id eFoo_##id; #include "foo.inl" #undef DEFINE_FOO #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) const char* get_foo_text1_t<tagFoo_##id>::get_foo_text1(){ return text1; } #include "foo.inl" #undef DEFINE_FOO #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) const char* get_foo_text2_t<tagFoo_##id>::get_foo_text2(){ return text2; } #include "foo.inl" #undef DEFINE_FOO
元のコードには指定されたものが範囲外だったら assert するという処理がありましたが、テンプレートでは指定されたものがあるか無いかといったことはコンパイル時に解決されるため、そうしたものは必要ありません
enum を使った実装
上記の例では get_foo_text1 と get_foo_text2 に渡された関数の引数から型を判別できるように enum ではなくタグ構造体を使うようにしてあります
この仕組みを template argument deduction を使った tag dispatching といいます
template argument deduction は以前にやりましたね。覚えていますか?
→ C++ 配列の要素数を静的に取得する方法
→ C++ コンパイル時に値の型を知る方法
enum の値を関数の引数に渡してしまうと渡された側の関数ではもうコンパイル時に enum の値を見ることはできなくなってしまいます
しかし enum の値を関数の引数ではなくテンプレートの引数に渡すのであれば渡された側の関数でも enum の値を見ることはできるので、関数の引数を使わなくても構わないなら enum で実装することもできます
foo.h
enum eFoo { #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) eFoo_##id, #include "foo.inl" #undef DEFINE_FOO elementsof_eFoo }; template<eFoo> struct get_foo_text1_t; #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) template<> struct get_foo_text1_t<eFoo_##id> { static const char* get_foo_text1(); }; #include "foo.inl" #undef DEFINE_FOO template<eFoo> struct get_foo_text2_t; #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) template<> struct get_foo_text2_t<eFoo_##id> { static const char* get_foo_text2(); }; #include "foo.inl" #undef DEFINE_FOO #define FOO_TEXT1( foo ) get_foo_text1_t<eFoo_##foo>::get_foo_text1() #define FOO_TEXT2( foo ) get_foo_text2_t<eFoo_##foo>::get_foo_text2()
foo.cpp
#undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) const char* get_foo_text1_t<eFoo_##id>::get_foo_text1(){ return text1; } #include "foo.inl" #undef DEFINE_FOO #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) const char* get_foo_text2_t<eFoo_##id>::get_foo_text2(){ return text2; } #include "foo.inl" #undef DEFINE_FOO
staticなメンバ変数を使った実装
上記の例では staticなメンバ関数を使って実装してありますが、 staticなメンバ変数を使って実装することもできます
foo.h
template<eFoo> struct get_foo_text1_t; #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) template<> struct get_foo_text1_t<eFoo_##id> { static const char* const value; }; #include "foo.inl" #undef DEFINE_FOO template<eFoo> struct get_foo_text2_t; #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) template<> struct get_foo_text2_t<eFoo_##id> { static const char* const value; }; #include "foo.inl" #undef DEFINE_FOO #define FOO_TEXT1( foo ) get_foo_text1_t<eFoo_##foo>::value #define FOO_TEXT2( foo ) get_foo_text2_t<eFoo_##foo>::value
foo.cpp
#undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) const char* const get_foo_text1_t<eFoo_##id>::value = text1; #include "foo.inl" #undef DEFINE_FOO #undef DEFINE_FOO #define DEFINE_FOO( id, text1, text2 ) const char* const get_foo_text2_t<eFoo_##id>::value = text2; #include "foo.inl" #undef DEFINE_FOO
静的な解決にはテンプレート
静的な解決にはテンプレートです