複数の(異なるかも知れない)型のオブジェクトが単一の関数から返される場合を想定する.
template< class T, class U > typename cradle::common_result< T const &, U const & >::type f( T const &t, U const &u, bool b ) { if( b ){ return t; } return u; }
このとき,上のコード片におけるメタ関数 cradle::common_result の設計と実装を行いたい,という問題.
まず,標準の規格 ISO/IEC-14882:2003 にある記述で問題領域をほぼ共有するものがあるのでそれを参考にする.(3項)条件演算子の戻り値の型に関する記述 (5.16/3), (5.16/4), (5.16/5), (5.16/6) がそれ.ちゃんとこれを説明するのは面倒なので適当な概略だけ示すと以下.
- 両 operand が arithmetic type (3.9.1/8) ならば usual arithmetic conversion (5/9) を適用
- 両 operand がポインタ型なら composite pointer type (5.9/2) を戻り値型として採用
- どちらか一方(もしくは両方)が user-defined type ならば, 片方の operand の型 (T とする) からもう片方の operand の型 (U とする) への暗黙の変換が可能かどうかを調べ,たとえば T から U へ暗黙に変換可能ならば型計算の結果は U となる.特記すべきは, T -> U, U -> T の変換が共に可能な場合 ill-formed なこと.変換が片方向であっても,適用できる変換が複数あり(たとえば変換コンストラクタと変換演算子が両方ある場合),変換が曖昧な場合も ill-formed
- 左辺値性は,両 operand が左辺値であり,かつ両 operand が同一の型,もしくは一方の operand が他方の基底クラスならば維持される.左辺値の場合, top-level の cv-qualification は両 operand の top-level cv-qualification の和となる
この規格をほぼトレースして実装したのがcradle::common_result.っていうかどういう型計算がやりたいのかは実物を見たほうが多分分かりやすいと思うのでコンパイル時単体テスト.
ちなみに,(3項)条件演算子の戻り値型計算,あるいは現在の cradle::common_result の実装は2項のメタ関数だけれど,3つの任意の(計算結果が定義される) operand T, U, V に関して結合則
BOOST_MPL_ASSERT(( is_same< cradle::common_result< cradle::common_result< T, U >::type, V >::type , cradle::common_result< T, cradle::common_result< U, V >::type >::type > ));
がふつーに成立するので n-arity (n>2) に対するメタ関数として自然に拡張することもできる(てか2項のを実装してるから一瞬でできるのになんでやってなかったんだっけ,俺?).
実装には usual arithmetic conversion と composite pointer type の型計算も必要になるので両者のエミュレーションも実装(これとこれ).少なくとも usual arithmetic conversion は手動 typeof 使ってかなり簡単にできるはずなのに何か問題があって結局諦めた記憶があるようなないような.
このメタ関数を実装しようとしたそもそもの動機,あるいは応用実例はかなり色々あるけれどたとえば以下.
#include <assert> #include <vector> #include <cradle/range/counting.hpp> #include <cradle/range/appended.hpp> #include <cradle/range/algorithm.hpp> #include <cradle/range/assign.hpp> namespace range = cradle::range; void test(){ std::vector< int > u; range::assign( range::counting( 0, 5 ), u ); std::vector< double > v; range::assign( range::counting( 5.0, 10.0 ), v ); typedef cradle::appended_range< std::vector< int >, std::vector< double > > Range; Range r( u, v ); // u の後方に v を連結したように見える Range を生成する. // usual arithmetic conversion によって int, double に共通の戻り値型 double が計算される. // double & -> double は左辺値性が維持できるが int & -> double は変換がかかるため非左辺値であり, // 全体の結果として戻り値は非左辺値として計算される.換言すると iterator の operator* は値返し. assert( range::equal( r, range::counting( 0.0, 10.0 ) ) ); }
ちなみに cradle::appended_range の実装はこれ. Range 周り (On-the-fly Range - 規則的な Range を on-the-fly で生成するもの / Range Adaptor - 既存の Range を別の形に lazy に変換するもの) の実装は SF に上げてないのを含めてこの発想に基づいて大量に作ってあるので今度紹介……できるのか?
というわけで,とりあえずこのブログの文面上だけでアイデアぶつぶつ書いてるだけでは何にもならん悪寒がするので,実装を実際に SF に上げてみました的な.てか,こういう SF の使い方はありなのか?