多相ファンクタのはなし
関数テンプレートは型がないというお話がありました。値もありません。
template <class T> T square(T x) { return x * x; } int main() { int array[] = {0, 1, 2, 3}; std::vector<int> v; // エラー!squareを引数として使用できない std::transform(array, array + 4, std::back_inserter(v), square); }
これでは持ち運べないので、クラスに押し込みます。
struct square { template <class T> T operator()(T x) const { return x * x; } }; int main() { int array[] = {0, 1, 2, 3}; std::vector<int> v; // OK std::transform(array, array + 4, std::back_inserter(v), square()); }
しかしクラスに入れるだけでは、Boostを使用した一部のコードで問題が生じます。
struct square { template <class T> T operator()(T x) const { return x * x; } }; int main() { // エラー! std::vector<int> const v = oven::counting(0, 4) | oven::transformed(square()) | oven::copied; }
この問題は、square const(int const &)の戻り値を導出できないために起こります。これを解決するためには、ネストされたクラステンプレートresultを追加して、squareをboost::result_ofに対応した関数オブジェクト(Polymorphic Function Object)にしなければなりません。
struct square { template <class> struct result; template <class F, class T> struct result<F(T)> : boost::decay<T> {}; template <class T> T operator()(T x) const { return x * x; }; }; int main() { // OK std::vector<int> const v = oven::counting(0, 4) | oven::transformed(square()) | oven::copied; }
あるいは、PStade.Eggを使うことができます。
struct square : egg::function_facade<square> { template <class F, class T> struct apply : boost::decay<T> {}; template <class R, class T> R call(T x) const { return x * x; }; }; int main() { // OK std::vector<int> const v = oven::counting(0, 4) | oven::transformed(square()) | oven::copied; }
C++0xのstd::result_ofはdecltypeベースの実装になるため、この問題がありません。boost::result_ofでも、マクロBOOST_RESULT_OF_USE_DECLTYPEを定義することにより、decltypeを使用することができます。
// C++0x #include <vector> #define BOOST_RESULT_OF_USE_DECLTYPE #include <pstade/oven/copied.hpp> #include <pstade/oven/counting.hpp> #include <pstade/oven/transformed.hpp> namespace oven = pstade::oven; struct square { template <class T> T operator()(T x) const { return x * x; } }; int main() { // OK std::vector<int> const v = oven::counting(0, 4) | oven::transformed(square()) | oven::copied; }
あわれVC10では、BOOST_NO_DECLTYPEが定義されてしまうため、上のコードは通りません。
最後に、この例はBoost.Lambdaを使うと簡潔に書けることを指摘しておきます。
int main() { using namespace boost::lambda; std::vector<int> const v = oven::counting(0, 4) | oven::transformed(_1 * _1) | oven::copied; }
おまけ
PStade.Ovenをひいきするのもアレなので、Boost.Rangeに差し替えてみましょう。
#include <iterator> #include <vector> #include <boost/range/adaptor/transformed.hpp> #include <boost/range/algorithm/copy.hpp> #include <boost/range/irange.hpp> #include <boost/type_traits/decay.hpp> struct square { template <class> struct result; template <class F, class T> struct result<F(T)> : boost::decay<T> {}; template <class T> T operator()(T x) const { return x * x; } }; int main() { std::vector<int> v; // エラー! boost::copy(boost::irange(0, 4) | boost::adaptors::transformed(square()), std::back_inserter(v)); }
このコードはBoost 1.46.0でコンパイルエラーになります。これは、transformedの実装に使われているboost::transform_iteratorが、boost::result_ofではなく貧相なオレオレresult_ofを使用しているために起こります。
https://svn.boost.org/trac/boost/ticket/1427
1年半以上前に最初のパッチが投げられているのに、なぜこんなに修正に時間がかかっているのでしょうか。
結論:(少なくとも現時点では)PStade.Ovenを使いましょう。