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;
}

型に応じて何かやりたいときはテンプレート

型に応じて何かやりたいときはテンプレートです