I keep hearing a lot about functors in C++. Can someone give me an overview as to what they are and in what cases they would be useful?
A functor is a higher-order function that applies a function to the parametrized(ie templated) types. It is a generalization of the map higher-order function. For example, we could define a functor for std::vector
like this:
template()(std::declval()))>
std::vector fmap(F f, const std::vector& vec)
{
std::vector result;
std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
return result;
}
This function takes a std::vector
and returns std::vector
when given a function F
that takes a T
and returns a U
. A functor doesn't have to be defined over container types, it can be defined for any templated type as well, including std::shared_ptr
:
template()(std::declval()))>
std::shared_ptr fmap(F f, const std::shared_ptr& p)
{
if (p == nullptr) return nullptr;
else return std::shared_ptr(new U(f(*p)));
}
Heres a simple example that converts the type to a double
:
double to_double(int x)
{
return x;
}
std::shared_ptr i(new int(3));
std::shared_ptr d = fmap(to_double, i);
std::vector is = { 1, 2, 3 };
std::vector ds = fmap(to_double, is);
There are two laws that functors should follow. The first is the identity law, which states that if the functor is given an identity function, it should be the same as applying the identity function to the type, that is fmap(identity, x)
should be the same as identity(x)
:
struct identity_f
{
template
T operator()(T x) const
{
return x;
}
};
identity_f identity = {};
std::vector is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector is1 = fmap(identity, is);
std::vector is2 = identity(is);
The next law is the composition law, which states that if the functor is given a composition of two functions, it should be the same as applying the functor for the first function and then again for the second function. So, fmap(std::bind(f, std::bind(g, _1)), x)
should be the same as 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 is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector is2 = fmap(to_foo, fmap(to_double, is));