2010-03-27

Variadic Template Parameterの落とし穴

Variadic Template Parameterの型引数のことを、テンプレートパラメーターパック(Template Parameter Pack)という。

template < typename ... Types >
struct Foo { }

ここでは、Typesは、テンプレートパラメーターパックである。

テンプレートパラメーターパックを、関数の引数に取る場合、これを、パラメーターパックという。

template < typename ... Types >
void f( Types ... args ) { }

ここでは、argsは、パラメーターパックである。

クラステンプレートの場合、テンプレートパラメーターパックは、必ず、テンプレートパラメーターリストの最後に書かなければならない。

// well-formed
template < typename T, typename ... Types >
struct well_formed ;

// ill-formed
template < typename ... Types, typename T >
struct ill_formed ;

これは、テンプレート実引数を指定しても、曖昧になるからである。

関数テンプレートのテンプレートパラメーターリストの場合には、そのような制限はない。なぜならば、関数テンプレートは、引数をdeduceできるからである。ただし、パラメーターパックは、関数のパラメーターリストの最後でなければならない。

// well-formed.
template < typename ... Types, typename T >
void well_formed( T, Types ... ) ;

// ill-formed.
// パラメーターパック、Typesは、パラメーターリストの最後にしか書けない。
template < typename ... Types, typename T >
void ill_formed( Types ..., T ) ;

これができれば、実に、メタプログラミング的な意味で、便利なのだが、残念ながら、これはできない。

ちなみに、こういう事はできる。これは、パラメータリストの型ではなく、パラメーターの型の一部だからである。

template < typename ... Types1, typename ... Types2 >
void f( void (*p1)( Types1 ...), void (*p2)( Types2 ... ) )
{
    // それぞれ、引数の型のデフォルトの値で呼び出す。
    p1( Types1()... ) ;
    p2( Types2()... ) ;
}


void g( int, int, int ) { }
void h( float, float ) { }


int main()
{
    f( &g, &h ) ;
}
// 呼び出すためだけの関数。
template < typename ... Types >
void call( Types ... ) { }

// 任意の戻り値を持つ関数ポインタを、任意個引数にとる。
template < typename ... Types >
void f( Types (* ... t)( void )  )
{
// 関数の実引数の評価順序は、定義されていない。
// どの順番で、引数の関数が呼び出されるかは、実装依存である。
// ただし、すべての関数は、呼び出される。
    call( t()... ) ;
}

int g( void ) { std::cout << "g" << std::endl ; return 0 ; }
int h( void ) { std::cout << "h" << std::endl; return 0 ; }


int main()
{
    f( &g, &h, &g, &h, &g, &h ) ;
}

このcall()関数はネタであって、普通の場合は、以下のような関数を書いた方が良い。

template < typename T >
void call( T t )
{
    t() ;
}

template < typename T, typename ... Types >
void call( T t, Types ... args )
{
    t() ;
    call( args ... ) ;
}

template < typename ... Types >
void f( Types (* ... t)( void )  )
{
    call( t... ) ;
}

これを組み合わせると、

template < typename ... ReturnTypes, typename ... ParameterTypes >
void f( ReturnTypes (* ... t)( ParameterTypes... )  ) { }

int g( char, short, int, long, float, double ) { return 0 ; }


int main()
{
    f( &g, &g, &g, &g ) ;
}

もちろん、これは、ジェネリックではない。しかし、この記述は、引数にとるすべての関数ポインタが、同じシグネチャであることを保証できる。普通のプログラマは、こう書けば良い。

template < typename ... Functors >
void f( Functors ... functors ) { }

No comments: