関数オブジェクトによる関数テンプレートのエミュレート

しかし死ぬほど忙しいのに,長文を.

http://d.hatena.ne.jp/mb2sync/20060124#p1

自分の場合,前々から汎用アルゴリズムを書く際はこの関数オブジェクトによる関数テンプレートのエミュレートで書いてきてたので,そのちんまい運用経験を元にいくつか書いてみる.
関数テンプレートを直接提供しないことによる利点は,自分が気付くものが以下(というか以下の点が自分が関数オブジェクトによるエミュレーションを導入したモチベーション).

  • 不用意な ADL が回避できる - オブジェクトの名前は ADL の対象にならないので不用意な ADL による lookup (std::equal 内部から mismatch が unqualified で呼ばれるとかの類の問題) が回避できる(この問題の C++ Standard Library Defect Report List における該当項目 その1その2その3).
  • コールバックが容易になる - 関数テンプレートをコールバックとして設定する場合,曖昧性解消のために明示的なキャストをかける必要がありますが,関数オブジェクトの場合その必要が無く簡単にコールバックとして設定できる.
  • 戻り値型推論機構の提供 - 上に書いたことと関連して, result メンバテンプレート + boost::result_of (std::tr1::result_of) によって,戻り値型の推論が可能になります.従来の(STL における)関数オブジェクトでは, value_type という内部型で戻り値の型を記述してましたが,これだと戻り値型を一つしか記述できず, operator() のオーバーロード(あるいはテンプレートの各インスタンス)ごとに戻り値の型が異なる場合に対処できませんでした.これが必要になる重要な実例はいくつもあります.関数オブジェクトで関数テンプレートをエミュレートすることによって operator() の戻り値型を複数記述することが可能になります.

実際に運用してみて,この関数オブジェクトによる関数テンプレートのエミュレートが真の関数テンプレートと異なる状況が生じる場合にも遭遇していて

  • テンプレート引数の明示的な指定ができない - 関数テンプレート f では f< Type >( x, y ) という構文でテンプレート引数を明示的に与えることができますが,関数オブジェクト f ではこれができない(できないというのは実際には半分ウソで f.operator()< Type >( x, y ) という構文で指定はできます.)
  • 特殊化できない - できないのは直接的なテンプレートの特殊化で,ユーザ定義型に対してカスタマイゼーションを提供する機構を提供したいなら,従来散々議論されてきた(ここの David Abrahams 氏と Peter Dimov 氏の議論がかなり参考になる)ように ADL hook point を関数テンプレートの形態で Global ADL namespace に提供すれば可能(Boost.Range での議論その1その2).関数オブジェクトの operator() の内部は単に unqualified で ADL hook を呼び出すだけ

前者の違いは例えば,

std::vector< int > v;
std::vector< int >::const_iterator e = v.end();
std::distance< std::vector< int >::const_iterator >( v.begin(), e );

というようなテンプレート引数の型推論の曖昧性解消の構文が書けないことを意味するのですが,これは例えば

template< class T >
T const &as_const( T const &x )
{ return x; }

のようなものを使って

std::vector< int > v;
std::vector< int >::const_iterator e = v.end();
std::distance( as_const( v ).begin(), e );

とかで解決できるかと思います.さらに深く突っ込むと David Abrahams 氏らが目指している方向性としては,アルゴリズム自体の引数に異なる型を指定できるようにする,というもののようです.つまり

template< class ForwardIterator >
typename std::iterator_traits< ForwardIterator >::difference_type
distance( ForwardIterator first, ForwardIterator last )
{
  .....
}

ではなくて

// ForwardCursor1 and ForwardCursor2 should be inter-operable.
template< class ForwardCursor1, class ForwardCursor2 >
typename std::iterator_traits< ForwardCursor2 >::difference_type
distance( ForwardCursor1 cosnt &first, ForwardCursor2 const &last )
{
  .....
}

という形でインタフェースを提供する,というものです(もちろん上に書いたように,実際には関数テンプレートではなく関数オブジェクトで提供するのもアリ).この形式のアルゴリズムのインタフェースは,単に異なる型の間の inter-operability への対処だけではなく,(アンロールのかかる cursor を universal end iterator で止めるなどの)静的なカスタマイゼーションの導入にも柔軟になるという副次的な利点もあります.それらがどこまで有用かは未知数ですけれど.
#もうそろそろここら辺の道具と議論は一定数が揃ってきた感があるので,次世代の STL 的な実装を誰かやらかしてくれないかにゃ〜.
#いやん. distance の戻り値の型書くの忘れてたワン.