C++ タイプセーフなエンディアン変換
はじめに
今、次のようなエンディアン変換の関数と構造体があるとしましょう
void swap_byte_order64( size_t celt, void* pelt ); void swap_byte_order32( size_t celt, void* pelt ); void swap_byte_order16( size_t celt, void* pelt ); typedef float float32_t; typedef double float64_t; //typedef long double float128_t; struct float128_t { float32_t f[4]; }; struct foo_t { uint64_t a; uint32_t b[2]; uint16_t c[4]; uint8_t d[8]; }; struct bar_t { foo_t e[2]; }; struct baz_t { bar_t f[3][4]; foo_t g; uint32_t h; };
ここで次のようにオブジェクト baz の各要素各メンバに対して適切なエンディアン変換の関数を呼びたいのですが、このままではとても面倒ですね
int main(){ baz_t baz[2] = {}; for( size_t n = 0; n < elementsof( baz ); ++n ){ // baz_t::f for( size_t m = 0; m < elementsof( baz[0].f ); ++m ){ for( size_t i = 0; i < elementsof( baz[0].f[0] ); ++i ){ for( size_t j = 0; j < elementsof( baz[0].f[0][0].e ); ++j ){ swap_byte_order64( 1, &baz[n].f[m][i].e[j].a ); swap_byte_order32( elementsof( baz[0].f[0][0].e[0].b ), baz[n].f[m][i].e[j].b ); swap_byte_order16( elementsof( baz[0].f[0][0].e[0].c ), baz[n].f[m][i].e[j].c ); } } } // baz_t::g swap_byte_order64( 1, &baz[n].g.a ); swap_byte_order32( elementsof( baz[0].g.b ), baz[n].g.b ); swap_byte_order16( elementsof( baz[0].g.c ), baz[n].g.c ); // baz_t::h swap_byte_order32( 1, &baz[n].h ); } return 0; }
これをなんとか簡単に書ける方法はないでしょうか
int main(){ baz_t baz[2] = {}; swap_byte_order( baz ); return 0; }
関数の呼び分けが面倒
さて、上記の例ではメンバの型に応じて呼び出す関数を変えていますね
64ビット用には swap_byte_order64
32ビット用には swap_byte_order32
16ビット用には swap_byte_order16
これが面倒ですし間違いの元で危険です
まずはこれを解消しましょう
問題は「型に応じて関数を呼び分けるのが面倒だ」ということでした
「型に応じて」と言えばテンプレートですね
template<typename T> inline void swap_byte_order( size_t celt, T* pelt ); template<> inline void swap_byte_order<uint64_t>( size_t celt, uint64_t* pelt ){ swap_byte_order64( celt, pelt ); } template<> inline void swap_byte_order<uint32_t>( size_t celt, uint32_t* pelt ){ swap_byte_order32( celt, pelt ); } template<> inline void swap_byte_order<uint16_t>( size_t celt, uint16_t* pelt ){ swap_byte_order16( celt, pelt ); }
このようなテンプレートを用意しておくと指定したオブジェクトの型に応じてコンパイラが勝手に呼び分けてくれるようになります
int main(){ baz_t baz[2] = {}; for( size_t n = 0; n < elementsof( baz ); ++n ){ // baz_t::f for( size_t m = 0; m < elementsof( baz[0].f ); ++m ){ for( size_t i = 0; i < elementsof( baz[0].f[0] ); ++i ){ for( size_t j = 0; j < elementsof( baz[0].f[0][0].e ); ++j ){ swap_byte_order( 1, &baz[n].f[m][i].e[j].a ); swap_byte_order( elementsof( baz[0].f[0][0].e[0].b ), baz[n].f[m][i].e[j].b ); swap_byte_order( elementsof( baz[0].f[0][0].e[0].c ), baz[n].f[m][i].e[j].c ); } } } // baz_t::g swap_byte_order( 1, &baz[n].g.a ); swap_byte_order( elementsof( baz[0].g.b ), baz[n].g.b ); swap_byte_order( elementsof( baz[0].g.c ), baz[n].g.c ); // baz_t::h swap_byte_order( 1, &baz[n].h ); } return 0; }
この指定したオブジェクトの型に応じて自動的に適切なテンプレート関数を呼び出してくれる仕組みを template argument deduction といいました
覚えていますか
要素数を指定するのが面倒
さて次に、上記の例ではいちいち配列の要素数を指定していますが、これも面倒ですし間違いの元で危険ですね
次はこれを解消しましょう
配列の要素数を指定したくないということは、配列でないなら 1 を、配列であるならその要素数を自動的に指定できるようにしたいということです
配列かどうかというのは「型が配列かどうか」ということです
「型に応じて」ということなのでこれもテンプレートで解決できます
template<typename T> inline void swap_byte_order( T& t ); template<typename T,size_t N> inline void swap_byte_order( T (&a)[N] ); template<> inline void swap_byte_order<uint64_t>( uint64_t& ll ){ swap_byte_order64( 1, &ll ); } template<> inline void swap_byte_order<uint32_t>( uint32_t& l ){ swap_byte_order32( 1, &l ); } template<> inline void swap_byte_order<uint16_t>( uint16_t& w ){ swap_byte_order16( 1, &w ); } template<size_t N> inline void swap_byte_order( uint64_t (&ll)[N] ){ swap_byte_order64( N, ll ); } template<size_t N> inline void swap_byte_order( uint32_t (&l)[N] ){ swap_byte_order32( N, l ); } template<size_t N> inline void swap_byte_order( uint16_t (&w)[N] ){ swap_byte_order16( N, w ); } int main(){ baz_t baz[2] = {}; for( size_t n = 0; n < elementsof( baz ); ++n ){ // baz_t::f for( size_t m = 0; m < elementsof( baz[0].f ); ++m ){ for( size_t i = 0; i < elementsof( baz[0].f[0] ); ++i ){ for( size_t j = 0; j < elementsof( baz[0].f[0][0].e ); ++j ){ swap_byte_order( baz[n].f[m][i].e[j].a ); swap_byte_order( baz[n].f[m][i].e[j].b ); swap_byte_order( baz[n].f[m][i].e[j].c ); } } } // baz_t::g swap_byte_order( baz[n].g.a ); swap_byte_order( baz[n].g.b ); swap_byte_order( baz[n].g.c ); // baz_t::h swap_byte_order( baz[n].h ); } return 0; }
ループに展開するのが面倒
上記の例では構造体の配列の各要素を処理するのにいちいちループを書かなくてはならないのですが、これも面倒ですし間違いの元で危険ですね
さらにこれを解消しましょう
構造体の配列だったら自動的にループを展開してほしいということは、「型が構造体の配列だったら」ということで型に応じた処理になるわけですからこれもはやりテンプレートで解決します
// 配列はループに展開 template<typename T,size_t N> inline void swap_byte_order( T (&value)[N] ){ for( size_t n = 0; n < N; ++n ){ swap_byte_order( value[n] ); } } // 構造体は個別に特殊化する template<> inline void swap_byte_order<foo_t>( foo_t& foo ){ swap_byte_order( foo.a ); swap_byte_order( foo.b ); swap_byte_order( foo.c ); } template<> inline void swap_byte_order<bar_t>( bar_t& bar ){ swap_byte_order( bar.e ); } template<> inline void swap_byte_order<baz_t>( baz_t& baz ){ swap_byte_order( baz.f ); swap_byte_order( baz.g ); swap_byte_order( baz.h ); } int main(){ baz_t baz[2] = {}; swap_byte_order( baz ); return 0; }
ようやくたどり着きました
安全で簡単になりましたね
全メンバに同じ関数を呼ぶのが面倒
しかしまだ上記の例では全メンバに対して機械的に同じ関数を呼ぶのが面倒です
また構造体にメンバを追加したときにはこちらの処理にも一緒に追加しないといけないので危険でもあります
最後にこれを解消しましょう
と言いたいところなのですが、C++ にはプログラム的に構造体のメンバを列挙するというような機能はないので、ここは人間が書く必要があります。残念
特定の型についての最適化
例えば float128_t は常に float32_t[4] として扱うというように、特定の型についての特別扱いを追加する場合はこんな感じに書きます
template<> inline void swap_byte_order<float128_t>( float128_t& v ){ swap_byte_order32( 4, &v ); } template<size_t N> inline void swap_byte_order( float128_t (&v)[N] ){ swap_byte_order32( 4*N, v ); }
多次元配列への最適化
上記のコードでは多次元配列を展開するのに再帰を使っていますが、ループに展開してしまってもいいでしょう
template<typename T,size_t N,size_t M> inline void swap_byte_order( T (&a)[N][M] ); template<size_t N,size_t M> inline void swap_byte_order( uint64_t (&ll)[N][M] ){ swap_byte_order64( N*M, ll ); } template<size_t N,size_t M> inline void swap_byte_order( uint32_t (&l)[N][M] ){ swap_byte_order32( N*M, l ); } template<size_t N,size_t M> inline void swap_byte_order( uint16_t (&w)[N][M] ){ swap_byte_order16( N*M, w ); } template<typename T,size_t N,size_t M> inline void swap_byte_order( T (&value)[N][M] ){ for( size_t n = 0; n < N; ++n ){ for( size_t m = 0; m < M; ++m ){ swap_byte_order( value[n][m] ); } } }
サンプル
// strict-aliasing-rules を前提にした最適化は無効にすること void debug_mem_fill( size_t cb, void* pv ){ uint8_t* pb = static_cast<uint8_t*>(pv); for( size_t n = 0; n < cb; ++n ){ uint8_t b = static_cast<uint8_t>(n&0x0ff); pb[n] = b; } } void debug_mem_dump( size_t cb, void* pv ){ if( cb <= 16 ){ const char* separator = ""; uint8_t* pb = static_cast<uint8_t*>(pv); for( size_t n = 0; n < cb; ++n ){ debug_printf( "%s%02x", separator, pb[n] ); separator = " "; } debug_printf( "\n" ); } else{ uint8_t* pb = static_cast<uint8_t*>(pv); for( size_t n = 0; n < cb; ++n ){ if( !( n & (16-1))){ debug_printf( "%08x", n ); } debug_printf( " %02x", pb[n] ); if( !((n+1) & (16-1))){ debug_printf( "\n" ); } } if( cb & (16-1)){ debug_printf( "\n" ); } } } void swap_byte_order16( size_t celt, void* pelt ){ debug_printf( "swap_byte_order16( %d ){\n", celt ); debug_mem_dump( celt*sizeof(uint16_t), pelt ); uint8_t(*w)[2] = static_cast<uint8_t(*)[2]>(pelt); for( size_t n = 0; n < celt; ++n ){ uint8_t (&b)[2] = w[n]; std::swap( b[0], b[1] ); } debug_printf( "↓\n" ); debug_mem_dump( celt*sizeof(uint16_t), pelt ); debug_printf( "}\n" ); } void swap_byte_order32( size_t celt, void* pelt ){ debug_printf( "swap_byte_order32( %d ){\n", celt ); debug_mem_dump( celt*sizeof(uint32_t), pelt ); uint16_t(*l)[2] = static_cast<uint16_t(*)[2]>(pelt); for( size_t n = 0; n < celt; ++n ){ uint16_t (&w)[2] = l[n]; swap_byte_order16( elementsof( w ), w ); std::swap( w[0], w[1] ); } debug_printf( "↓\n" ); debug_mem_dump( celt*sizeof(uint32_t), pelt ); debug_printf( "}\n" ); } void swap_byte_order64( size_t celt, void* pelt ){ debug_printf( "swap_byte_order64( %d ){\n", celt ); debug_mem_dump( celt*sizeof(uint64_t), pelt ); uint32_t(*ll)[2] = static_cast<uint32_t(*)[2]>(pelt); for( size_t n = 0; n < celt; ++n ){ uint32_t (&l)[2] = ll[n]; swap_byte_order32( elementsof( l ), l ); std::swap( l[0], l[1] ); } debug_printf( "↓\n" ); debug_mem_dump( celt*sizeof(uint64_t), pelt ); debug_printf( "}\n" ); } struct foo_t { uint64_t aaa; uint32_t bbb[2]; uint16_t ccc[4]; uint8_t ddd[8]; float128_t eee[4]; }; struct bar_t { foo_t fff[2]; }; struct baz_t { bar_t ggg[3][4]; foo_t hhh; uint32_t iii; float128_t jjj; }; template<typename T> inline void swap_byte_order( T& t ); template<typename T,size_t N> inline void swap_byte_order( T (&a)[N] ); template<> inline void swap_byte_order<uint64_t>( uint64_t& ll ){ swap_byte_order64( 1, &ll ); } template<> inline void swap_byte_order<uint32_t>( uint32_t& l ){ swap_byte_order32( 1, &l ); } template<> inline void swap_byte_order<uint16_t>( uint16_t& w ){ swap_byte_order16( 1, &w ); } template<> inline void swap_byte_order<uint8_t>( uint8_t& ){ } template<> inline void swap_byte_order<float128_t>( float128_t& v ){ swap_byte_order32( 4, &v ); } template<size_t N> inline void swap_byte_order( uint64_t (&ll)[N] ){ swap_byte_order64( N, ll ); } template<size_t N> inline void swap_byte_order( uint32_t (&l)[N] ){ swap_byte_order32( N, l ); } template<size_t N> inline void swap_byte_order( uint16_t (&w)[N] ){ swap_byte_order16( N, w ); } template<size_t N> inline void swap_byte_order( float128_t (&v)[N] ){ swap_byte_order32( 4*N, v ); } template<typename T,size_t N> inline void swap_byte_order( T (&a)[N] ){ for( size_t n = 0; n < N; ++n ){ swap_byte_order( a[n] ); } } template<> inline void swap_byte_order<foo_t>( foo_t& foo ){ swap_byte_order( foo.aaa ); swap_byte_order( foo.bbb ); swap_byte_order( foo.ccc ); swap_byte_order( foo.ddd ); swap_byte_order( foo.eee ); } template<> inline void swap_byte_order<bar_t>( bar_t& bar ){ swap_byte_order( bar.fff ); } template<> inline void swap_byte_order<baz_t>( baz_t& baz ){ swap_byte_order( baz.ggg ); swap_byte_order( baz.hhh ); swap_byte_order( baz.iii ); swap_byte_order( baz.jjj ); } int main(){ baz_t baz[2][3] = {}; debug_mem_fill( sizeof( baz ), baz ); debug_printf( "{\n" ); debug_mem_dump( sizeof( baz ), baz ); debug_printf( "}\n" ); swap_byte_order( baz ); debug_printf( "{\n" ); debug_mem_dump( sizeof( baz ), baz ); debug_printf( "}\n" ); return 0; }
型に応じて何かやりたいときはテンプレート
型に応じて何かやりたいときはテンプレートです