関数テンプレートに対するデフォルトテンプレート引数の使い方

この記事は、新しい使い道が発見され次第、随時更新していく予定です。

はじめに

C++0x では、関数テンプレートに対し、デフォルトのテンプレート引数を渡すことが出来るようになりました:
http://d.hatena.ne.jp/faith_and_brave/20071105/1194259443

// 上記 URL から転載
template <class T, class U = double>
void f( T t = 0, U u = 0 );

void g()
{
  f(1, 'c');         // f<int,char>(1,'c')
  f(1)               // f<int,double>(1,0)
  f();               // error: T cannot be deduced
  f<int>();          // f<int,double>(0,0)
  f<int,char>();     // f<int,char>(0,0)
}

しかし、上の例を見ただけだと、
「こんな機能、いったい誰が使うんだ」
と思うかも知れません。


http://d.hatena.ne.jp/pastel-magic/20090521/1242854172
の記事でも、
「こんな機能絶対使わないと思ってました」
C++0xC++より、さらにコードが奇妙になりそうです」
と、散々な言われよう。


実際、上のコードでは、デフォルトテンプレート引数を使わずとも、関数多重定義により、

template<class T>
void f( T t = 0, double u = 0 );

template<class T, class U>
void f( T t = 0, U u = 0 );

と書けば、それで済む問題です。
そこをあえてデフォルト引数を使って書く意味は、正直、あまりないでしょう。


しかし、ここでハッキリ言わせていただきます。
関数テンプレートに対するデフォルト引数は、実のところ、
auto やラムダ式に匹敵するレベルで、 C++ のコードを劇的に読みやすく改善してくれる、
とても素晴らしい機能なのです。


その威力は、一度使ったら、もう手放せなくなるレベル。
というわけで、この記事では、 C++0x の大きな改善の一つ、
「関数テンプレートに対するデフォルトテンプレート引数」
の使い方を説明したいと思います。

使用法1: その名の通り、「デフォルト引数」として使う

一番最初に挙げた使い方です。


いかにもサンプル的な例はさっき出したので、実用的な例を挙げると、

#include <memory>   // for std::unique_ptr
#include <utility>  // for std::forward

template< class T, class D = std::default_delete<T> >
inline std::unique_ptr<T, D> make_unique_ptr( T* p, D d = D() )
{
  return std::unique_ptr<T, D>( p, std::forward<D>(d) );
}

#include <cstdlib>  // for std::malloc, std::free

int main()
{
  auto p1 = make_unique_ptr( new int() ); // D はデフォルト
  auto p2 = make_unique_ptr( std::malloc( 10 ),
    []( void* p ){ std::free(p); }  // D をユーザ指定
  );
}

このように使うことが考えられます。


上記の関数テンプレート make_unique_ptr は、与えられた引数から std::unique_ptr を作る関数です。
これは、基本的には得られたポインタとデリータの組から std::unique_ptr を作りますが、
一番多いケースであろう、普通に new されたポインタから std::unique_ptr を作る場合は、
デリータを省略することが出来、その場合は std::default_delete になります。


もちろんこれは、二つに分けて

// 本体のみ記述
template<class T>
inline std::unique_ptr<T> make_unique_ptr( T* p )
{
  return std::unique_ptr<T>( p );
}
template<class T, class D>
inline std::unique_ptr<T, D> make_unique_ptr( T* p, D d = D() )
{
  return std::unique_ptr<T, D>( p, std::forward<D>(d) );
}

と書いても同じです。
しかし、同じような関数を二回書くのは、単純に面倒。
というわけで、そういう時に役に立ってくれる機能です。

まぁぶっちゃけた話、他の使われ方に比べると あまり使われませんが

使用法2: 型を変数に束縛するために使う

さて、ここからが本番です。


最初の例としてあげた関数テンプレート make_unique_ptr をもう一度ご覧ください。

template<class T, class D = std::default_delete<T>>
inline std::unique_ptr<T, D> make_unique_ptr( T* p, D d = D() )
{
  return std::unique_ptr<T, D>( p, std::forward<D>(d) );
}

この関数の定義には、型 std::unique_ptr が二回使われている事にお気づきでしょうか。
すなわち、関数の戻り値を指定する部分と、実際に関数の戻り値を生成する部分、の二回です。


そして、この二つの型は、たまたま一致したものではなく、必然的に同じ型となっています。
このように、必然的に同じ型になる部分を、その都度 完全な型名として書いていくのは、単純に面倒ですし、
また、何らかの変更があった場合…例えば、戻り値の型を std::unique_ptr から
std::unique_ptr::type> に変更した場合*1

template<class T>
struct decay_and_strip { typedef 〜 type; };

template<class T, class D = std::default_delete<T>>
inline std::unique_ptr<T, typename decay_and_strip<D>::type>
  make_unique_ptr( T* p, D d = D() )
{
  return std::unique_ptr<T, typename decay_and_strip<D>::type>(
    p, std::forward<D>(d)
  );
}

こんな感じになり、更に面倒ですし、変更の際にうっかりミスが起きる可能性もあるでしょう。


デフォルトテンプレート引数の真価は、こういう時に発揮されます。
これを使うと、この後 使う型に対し、

template< class T, class D = std::default_delete<T>,
  class D_ = typename decay_and_strip<D>::type, // メタ関数の結果として得られた型に名前をつける
  class Result = std::unique_ptr<T, D_> // その型を使って、戻り値の型も計算し、名前をつける
>
inline Result make_unique_ptr( T* p, D d = D() )  // あとは単純に
{
  return Result( p, std::forward<D>(d) ); // 名前 'Result' を使えばいい。ミスする余地はない。
}

このように名前を付けることが可能になります。
これは要するに、関数内部における typedef と殆ど同じような意味を持ちます:

template<class T>
void print( T const& x )
{
  typedef typename T::const_iterator iterator; // 型に別名を付け
  
  // その別名を使う。
  for( iterator iter = x.begin(), end = x.end(); iter != end; ++iter ) {
    std::cout << *iter << std::endl;
  }
  
}

関数内部の typedef と違うのは、戻り値の型として使える点であり、
特に、戻り値の型を複雑な型計算で求める必要がある場合、
関数内部の typedef だと、その型計算を改めて行う必要がありますが、
この方法ならばその必要はありませんし、
また型計算自身も、名前がある分だけ読みやすくなります。


これにより、関数の書きやすさと読みやすさを大幅に上昇させることが可能になり、
特に短い関数では、その効果はかなり大きくなります。

なお、少々本題からは話が逸れますが、実はさっきの例では戻り値の型を二回書かなくても大丈夫で、
Uniform Initialization を使えば

template<class T, class D = std::default_delete<T>>
inline std::unique_ptr<T, typename decay_and_strip<D>::type>
  make_unique_ptr( T* p, D d = D() )
{
  return { p, std::forward<D>(d) };
}

と、同じ型名を二度書く手間を省くことが出来ます。
しかし、この表記はいつでも使えるというものではなく、例えば std::tuple のコンストラクタのように explicit 指定されている場合

template<class... Args>
inline std::tuple<typename decay_and_strip<Args>::type...>
  make_tuple( Args&&... args )
{
  return { std::forward<Args>(args)... }; // こうは書けない
                                          // 何故なら tuple を要素から構築するコンストラクタは
                                          // explicit 指定されているため
}

や、 std::vector のように std::initializer_list を取るコンストラクタがある場合

template<class T>
std::vector<T> make_vector( std::size_t n, T const& x ) {
  return { n, x }; // こう書くと std::initializer_list<T> の方が推論されるので
}

auto vec = make_vector( 5, 1 ); // <del>この結果は { 1, 1, 1, 1, 1 } ではなく { 5, 1 } となる</del>
                                // コンパイルエラー. { 1, 1, 1, 1, 1 } なのか { 5, 1 } なのか決定できない 

には上手くいきません。
というわけで、工夫すれば同じ型を二回書かなくていいような場合でも、
変に工夫するよりは、素直にデフォルトテンプレート引数を使った方が、結果的に無難になるかと思います。


閑話休題


また、前の例とは別に、戻り値の型として decltype を使う場合のことを考えましょう。
その場合も、デフォルトテンプレート引数は有効で、 std::declval と組み合わせて

template<class T,
  class Result = decltype( f( std::declval<T>() ) ) // f は適当な関数
>
inline Result g( T && x );
// template<class T>
// inline decltype( f( std::declval<T>() ) ) g( T && x );
// より読みやすい

このように書くことが出来ます。
もっともこの場合は

template<class T>
inline auto g( T && x )
  -> decltype( f( std::forward<T>(x) ) );

と同じですが、 gcc-4.5.0 の場合、前者ではエラーにならないのに後者では何故かエラーになる場合がある*2し、
地味に std::forward(x) より std::declval() の方が文字数が少ないという利点もあります。
また、 auto では無理な例としては、

template<class X>
??? do_something( X && x ) {
  auto && y = f( x );
  auto && z = g( y, std::forward<T>(x) );
  return h( std::forward<decltype(z)>(z), std::forward<decltype(y)>(y) );
}

のような処理を書きたい*3けど、じゃあこの関数の戻り値の型はどうしよう、という場合などが考えられ、
この場合にも、デフォルトテンプレート引数を使うことで、

template< class X,
  class Y = decltype( f( std::declval<X&>() ) ),
  class Z = decltype( g( std::declval<Y&>(), std::declval<X>() ) ),
  class Result = decltype( h( std::declval<Z&>(), std::declval<Y&>() ) )
>
Result do_something( X && x ) {
  auto && y = f( x );
  auto && z = g( y, std::forward<T>(x) );
  return h( std::forward<decltype(z)>(z), std::forward<decltype(y)>(y) );
}

このように、きちんと決定することが可能になるのです。

使用法3: SFINAE

C++ テンプレートを使っていると、しばしば、条件によって呼び出す関数を切り替えたい場合が生じます。
例えば、 boost::lexical_cast を使う時のことを考えましょう。
この関数は、文字列表現を媒介に型変換を行う関数であり、数値から文字列の変換やその逆を汎用的に行なえますが、その実装は汎用性を重視しているため、やや効率に劣る点があります。
一方で、 C++0x の標準ライブラリには、数値から文字列の変換を効率的に行う std::to_string や std::to_wstring といった関数があります。しかし一方で、これは数値から文字列への変換にしか使えない関数です。
ここで、この二つを合体させることを考えます。つまり、数値から文字列に変換するときは標準ライブラリの変換関数を、そうでない場合には boost::lexical_cast を使えば、効率と汎用性を両立させることが出来そうです。
と、前置きが長くなりました。そのような場合に使われる C++ コンパイラの動作原理が、 SFINAE という仕組みなのですが、*4
デフォルトテンプレート引数を使うと、この処理を、今までより分かりやすく書く事が可能になります。


なお、時間の節約のため、以降の説明は C++98/03 における SFINAE を知っている人を対象に書かせていただきます。


さて、 C++98/03 において SFINAE を行うには、関数の戻り値か、関数のダミー引数に boost::enable_if を使うことで実現していました。
C++0x では、標準ライブラリに std::enable_if が追加され*5、 enable_if の他に decltype 等による SFINAE も可能になったりと、様々な点で進歩しています。


例として、 decltype による SFINAE の例を挙げましょう。
これは主に戻り値として使われ、

template<class X>
inline auto begin( X& x )
  -> decltype( x.begin() )
{
  return x.begin();
}

上記のコードにおいて、 x.begin() というメンバ関数呼び出しが出来ない場合、
この関数は SFINAE により呼び出す関数の候補から外され、他の関数が探されます。
このコードにおける decltype( x.begin() ) は、 x.begin() の型を取得すると同時に、
x.begin() という式そのものが有効であるか否かを判別し、無効であってもエラーはしないで
別の関数を探しに行く、という動作を、ごく自然に行なっています。
このように C++0x では、殆どの SFINAE はごく自然に行われるようになり、非常に扱いやすくなっています。


さて、このように扱いやすくなった SFINAE ですが、
その中でも、特に大事な進歩が、関数テンプレートのデフォルトテンプレート引数において SFINAE が行えるようになった点であり、これと decltype による SFINAE を組み合わせることにより、

template< class F, class T,
  class = typename std::enable_if<
    std::is_explicitly_convertible<T&, bool>::value // T& が bool に変換可能で、
  >::type,
  class R = typename std::decay<
    decltype( std::declval<F>()( *std::declval<T>() ) ) // f( *t ) という関数呼び出しの戻り値を
  >::type,                                              // コピーした型を R とした時
  class = typename std::enable_if<
    std::is_default_constructible<R>::value // R がデフォルト構築可能なら、その時に限り、
  >::type
>
inline R apply_if( F && f, T && t ) { // apply_if という関数が
  return t ? std::forward<F>(f)( *std::forward<T>(t) ) : R(); // このように定義される
}

上記のような複雑な条件を、可読性よく指定できるようになりました。


また、従来の C++ では、コンストラクタにおける SFINAE では戻り値の型が使えないため、ダミー引数を使うしか無かったのが、
C++0x ではデフォルトテンプレート引数を使うことが可能になったため、

template<class T>
struct X
{
  T value;
  
  template< class... Args,
    class = typename std::enable_if<
      std::is_constructible<T, Args...>::value
    >::type
  >
  explicit X( Args&&... args )
    : value( std::forward<Args>(args)... ) {}
  
};

上記のように、非常にスッキリとした書き方が可能になっています。


このように、 C++0x の SFINAE とデフォルトテンプレート引数は、非常に親和力が高いため、
C++98/03 では面倒だった複雑なコードも、かなり楽に書けるようになっています。
さらに複雑な例などは、拙ライブラリ Etude の実装や、
この記事の最後のおまけに記した、 boost::lexical_cast の実装を一部 std::to_w?string に切り替えるコードを見るといいでしょう。


…あ、そうそう、一応言っておきますが、
大部分の C++er は、この章の内容は理解する必要はありません。
「変なコト言ってる…世の中にはこういう変態も居るんだなぁ、気持ち悪い。」
程度の認識でも、普通に C++0x を使う分にはマズ問題ないので、そこは誤解なきよう。

さいごに

さて、このように便利なデフォルトテンプレート引数ですが、実はこれには弱点もあり、
これはあくまで「デフォルト引数」に過ぎないので、関数呼び出しの際に明示的に指定された場合、
その関数の意味論は完膚なきまでに破壊されてしまう、という点が挙げられます:

template< class T, class D = std::default_delete<T>,
  class D_ = typename decay_and_strip<D>::type,
  class Result = std::unique_ptr<T, D_>
>
inline Result make_unique_ptr( T* p, D d = D() )
{
  return Result( p, std::forward<D>(d) );
}

// うへぇ
auto p = make_unique_ptr<int, int, void, std::pair<void*, int>>( 0 );

まぁ、こういうのは、ぶっちゃけた話「そう書くほうが悪い」のですが、
一応対策も無いわけではなく、ラッパー関数を使い、

template< class T, class D,
  class D_ = typename decay_and_strip<D>::type,
  class Result = std::unique_ptr<T, D_>
>
inline Result make_unique_ptr_( T* p, D d )
{
  return Result( p, std::forward<D>(d) ); // 名前 'Result' を使えばいい。ミスする余地はない。
}

template< class T, class D = std::default_delete<T> >
inline auto make_unique_ptr( T* p, D d = D() )
  -> decltype( make_unique_ptr_( p, std::forward<D>(d) ) )
{
  return make_unique_ptr_( p, std::forward<D>(d) );
}

// 当然のようにエラー。
auto p = make_unique_ptr<int, int, void, std::pair<void*, int>>( 0 );

と書けば問題ありません。…が、正直、そこまでする必要はない気もします。


ただ、テンプレート引数を明示的に渡すタイプの関数テンプレートがオーバーロードされていた場合など、
複雑なケースでは、そういう仕様なのかコンパイラのバグかは分かりませんが、
デフォルトテンプレート引数を使った場合、該当する関数を見つけられない事があります。
そのような場合にはラッパーを使うのも一つの手かもしれませんので、覚えておいて損はないでしょう。

まとめ

さて今回、いろいろと説明しましたが、実は僕の伝えたかったことは一つです。


つまり、関数テンプレートのデフォルトテンプレート引数っていうのは、

template< class T,
  class U = std::decay<T>::type,
  class R = std::pair<U, U>,
  class = typename std::enable_if<
    std::is_copy_constructible<U>::value
  >::type
>
inline R fork( T && x ) {
  T y = x;
  return R( std::forward<T>(x), std::forward<T>(y) );
}

このように、戻り値の型計算や、制約条件の明示を行う場合に、
ちょっとした型変数として使うことで、可読性を大きく上げられるものだ、ということ。


言い換えるなら、これは auto や lambda や range-based for 等の、 C++0x の多くの機能と同じように、
本質的に出来ることは今までとあまり変わらないけど、あるだけで一気にコードが読みやすくなる
という類のものであり、逆に言えば、これを知らないのは全くの損だ、ということです。


ということで、 C++0x のコードを書くときは、是非ともこの小粋な新しい仲間をお供にすると、
読みやすいコードを書く為に、一役も二役も立ってくれるのではないかな、と思います。


あ、これ、 gcc だと 4.3.0 から使えます。
コンパイル時にオプション -std=c++0x と付けることを忘れずに。

// VC++? なにそれ美味しいの?

おまけ

std::to_string や std::to_wstring が呼べる場合には、そちらを呼ぶことで効率化した俺々 boost::lexical_cast の実装は、こちらになります。

// 注: gcc-4.5.0 には std::to_string が無いので、
// このコードはコンパイル通りません。あくまでイメージです。
#include <string>
#include <boost/lexical_cast.hpp>
#include <utility>
#include <iostream>

// デフォルトの実装
template< class To, class From >
To my_lexical_cast_( From && x, ... ) {
  std::cout << "default version\n";
  return boost::lexical_cast<To>( std::forward<From>(x) );
}
// to_string に対して特殊化された実装
template< class To, class From,
  class = typename std::enable_if<
    std::is_same< To,
      decltype (
        std::to_string( std::declval<From>() )
      )
    >::value
  >::type
>
To my_lexical_cast_( From && x, int ) {
  std::cout << "to_string version\n";
  return std::to_string( std::forward<From>(x) );
}
// to_wstring に対して特殊化された実装
template< class To, class From,
  class = typename std::enable_if<
    std::is_same< To,
      decltype (
        std::to_wstring( std::declval<From>() )
      )
    >::value
  >::type
>
To my_lexical_cast_( From && x, void* ) {
  std::cout << "to_wstring version\n";
  return std::to_wstring( std::forward<From>(x) );
}

// ラップ関数
template< class To, class From >
To my_lexical_cast( From && x ) {
  return my_lexical_cast_<To>( std::forward<From>(x), 0 );
}

int main()
{
  auto a = my_lexical_cast<int>( "10" );          // default version
  auto b = my_lexical_cast<std::string>( &a );    // default version
  auto c = my_lexical_cast<std::string>( 10 );    // to_string version
  auto d = my_lexical_cast<std::wstring>( 3.14 ); // to_wstring version
}

*1: typename decay_and_strip::type が実際には どんな型に定義されるかは今回は特に気にしませんが、イメージ的には std::make_pair や std::make_tuple で行われる処理だとお考えください

*2:そうなる条件は詳しく検証していないのは不明。開発版では改善されてるかも不明。

*3:実際にこういう処理を関数として切り出すのはあまり良くないですが、そういう汚いコードが必要になる場合もあります。 C++ だもの。

*4:といっても、ここでは詳しくは説明しません。詳しくは検索してください。

*5:これは boost::enable_if_c に相当するもので、動作は微妙に違うのですが、やりたいことは同じです。