多相ファンクタのはなし

関数テンプレートは型がないというお話がありました。値もありません。

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を使いましょう。