Функтор - это функция высшего порядка , которая применяет функцию к параметризованным (то есть шаблонным) типам. Это обобщение функции map высшего порядка. Например, мы могли бы определить функтор для std::vector
следующим образом:
template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
std::vector<U> result;
std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
return result;
}
Эта функция принимает std::vector<T>
и возвращает std::vector<U>
, когда ей передана функция F
, которая принимает T
и возвращает U
. Функтор не обязательно должен быть определен для типов контейнеров, он также может быть определен для любого шаблонного типа, включая std::shared_ptr
:
template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
if (p == nullptr) return nullptr;
else return std::shared_ptr<U>(new U(f(*p)));
}
Вот простой пример, который преобразует тип в double
:
double to_double(int x)
{
return x;
}
std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);
std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);
Есть два закона, которым должны следовать функторы. Первый - это закон тождества, который гласит, что если функтору задана функция тождества, он должен быть таким же, как применение функции тождества к типу, то есть fmap(identity, x)
должно совпадать с identity(x)
:
struct identity_f
{
template<class T>
T operator()(T x) const
{
return x;
}
};
identity_f identity = {};
std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);
Следующим законом является закон композиции, который гласит, что если функтору дана композиция из двух функций, он должен быть таким же, как применение функтора для первой функции, а затем снова для второй функции. Итак, fmap(std::bind(f, std::bind(g, _1)), x)
должно совпадать с fmap(f, fmap(g, x))
:
double to_double(int x)
{
return x;
}
struct foo
{
double x;
};
foo to_foo(double x)
{
foo r;
r.x = x;
return r;
}
std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));