I’ve noticed that in the C++ vernacular, function objects are frequently referred to as functors.

When I say function object, I’m referring to anything that can be called with ().

(A function object shouldn’t be confused with a Callable, which has a specific definition to support std::invoke.)

Most C++ programmers can probably agree that C++11 lambda expressions changed their lives. The rest are probably those who were lucky to start learning the language in C++11. Lambdas are an elegant replacement for what many call “functor structs”: types that overload operator() so they can be used like functions while maintaining persistent state between calls.

If you don’t believe me, see the answer to this question, which over 600 people have upvoted on Stack Overflow.

Perhaps “functor struct” and “function object” have been conflated because “functor” and “function” sound very similar. But the original meaning of “functor” is different from what Stack Overflow decided was “correct”.

Formally, a functor is a mapping between categories that preserves identity morphisms and compositions of morphisms.

Unless you’ve studied category theory, that definition probably means nothing to you! It’s also not incredibly useful in C++, since we don’t commonly deal with things like as “categories” and “morphisms. However, you might be using functors without even knowing it (real functors, not “functor structs”).

A functor is a container that knows how to map a function over the stuff in the container to another container holding new stuff.

No, I don’t mean a C++ Container as defined in the standard library. The container can be anything that provides a computational context.

More formally, a functor is a homomorphism between categories: a mapping that maps the identity element in one category to the identity element in another category, as well as preserving composition of mappings in each category.

Functors are usually described at the same time as Applicatives and Monads. Applicatives (stands for applicative functor) map from a context containing mappings and a context containing values to a context containing the result of the mappings applied to those values. Monads are applicatives that provide a mapping from a wrapped value and a function that returns the same kind of wrapped value to another wrapped value.

For a longer explanation and some great illustrations, check out this article.

You’ll notice that the author of that post uses Haskell as an example. I suspect that most C++ programmers who actually understand functors are Haskell hackers in disguise. That’s because in Haskell, these abstract category theory ideas are a core part of the language in the form of typeclasses.

For an “inheritance diagram” describing the relationships between typeclasses, see this tree from the Haskell wiki.

But why should we care about Haskell’s functors? We’ve co-opted the word to mean something else, so why do we need to start using a definition from a completely different, more obscure programming language? It’s awfully prescriptivist for me to correct the language of the majority of the C++ community, isn’t it? (If you’re not familiar with prescriptivism, go read a linguistics textbook, or Wikipedia.)

We should stop conflating function objects and functors because ideas from category theory are actually useful for manipulating types in C++, and we should use different words to disambiguate between these ideas.

A function and a functor are both mappings. The difference is that the function is a runtime mapping between values, and the functor is a mapping between categories.

If you’re a template metaprogramming enthusiast, this probably isn’t news for you. You’ve probably also heard of and used Boost.Hana, a notable library that gets the definition of Functor right.

Hana provides implementations of many useful functional programming ideas. In fact, it imitates the Haskell typeclass idea by emulating Concepts (the proposed syntactic interface for providing logical constraints on templates using predicates), and provides C++ equivalents of many Haskell typeclasses, such as Functors, Applicatives, and Monads. Defining a type (usually a template type) that conforms to one of these concepts gives you access to a powerful set of utilities for manipulating types.

To make these abstract category theory ideas more concrete, let’s dive into an implementation of the Hana Monad concept.

Suppose we implement a burrito class templated on zero or more filling types. It provides a factory function for making a new burrito and it models the Functor, Applicative, and Monad concepts.

    auto bb = make_burrito(beans(50.0), rice(100.0), beef(100.0));

    std::cout << "Beef burrito: \n" << bb << std::endl;

    // Check that the burrito type conforms to the required concepts.
    static_assert(hana::Functor<decltype(bb)>::value,
                  "Burrito instance does not model Functor concept.");
    static_assert(hana::Applicative<decltype(bb)>::value,
                  "Burrito instance does not model Applicative concept.");
    static_assert(hana::Monad<decltype(bb)>::value,
                  "Burrito instance does not model Monad concept.");

We also define a few valid operations on food types.

  auto monadic_fry = [](auto &&f) {
    if constexpr(is_food_v<decltype(f)>) {
      f.fat += 14;
      return make_burrito(f);
    } else {
      return make_burrito();  // Represents an "invalid burrito"
    }
  };

  auto monadic_salt = [](auto &&f) {
    if constexpr(is_food_v<decltype(f)>) {
      f.sodium += 0.002;
      return make_burrito(f);
    } else {
      return make_burrito();  // Represents an "invalid burrito"
    }
  };

  auto salt = [](auto &&f) {
    f.sodium += 0.002;
    return f;
  };

Modelling “Functor” allows us to use the hana::transform utility on a burrito to apply an operation, fry, on all the individual fillings of the burrito.

Modelling “Monad” allows us to use flatten to flatten multiple burritos nested inside a burrito into one burrito.

    auto cb = make_burrito(beans(50), rice(100), chicken(100));
    std::cout << "Chicken burrito: \n" << cb << std::endl;

    auto monster_burrito = make_burrito(std::move(cb), std::move(bb));
    std::cout << "Nested monster burrito: \n" << monster_burrito << std::endl;

    // Flatten burrito levels.
    auto merged_burrito = hana::flatten(std::move(monster_burrito));
    std::cout << "Merged burrito: \n" << merged_burrito << std::endl;

“Functor” also allows us to apply a function selectively to the contents of the burrito based on a predicate:

    auto bb = make_burrito(beans(50), rice(100), beef(50));
    // You can selectively apply an operation to the ingredients of the burrito.
    auto needs_salt = [](auto&& f){
      if constexpr(is_food_v<decltype(f)>) {
        return f.sodium < 0.0002;
      } else {
        // Can't apply salt to something that isn't food
        return false;
      }
    };
    auto salted_bb = hana::adjust_if(std::move(bb), needs_salt, salt);
    std::cout << "With salted rice and beans: \n" << salted_bb << std::endl;

And “Monad” allows us to compose operations on the contents of the burritos.

    auto cb = make_burrito(beans(50), rice(100), chicken(50));
    auto burrito_pipeline = hana::monadic_compose(monadic_fry, monadic_salt);
    auto combo_burrito = burrito_pipeline(std::move(cb));

    std::cout << "Salty fried chicken burrito:\n" << combo_burrito << std::endl;

To implement these concepts for your own data types, you need to define a tag type for your type, which can just be an empty struct, and make a specialization of hana::tag_of:

struct burrito_tag {};

template <typename... T> struct hana::tag_of<burrito<T...>> {
  using type = burrito_tag;

To model Functor, we need to write a specialization of hana::transform_impl for our tag type and implement the apply function:

// Functor
template <> struct hana::transform_impl<burrito_tag> {
  template <std::size_t... i, typename F, typename... Fs>
  static constexpr auto transform_helper(std::index_sequence<i...>, F &&f,
                                         std::tuple<Fs...> &&t) {
    return make_burrito(f(std::get<i>(t))...);
  }

  // Pull out the burrito's contents, apply the function
  // over the contents, and wrap it into a new burrito.
  template <typename F, typename... Fs>
  static constexpr auto apply(burrito<Fs...> &&b, F &&f) {
    return transform_helper(std::index_sequence_for<Fs...>{},
                            std::forward<F>(f), unwrap_fillings(b));
  }
};

The basic idea applies to the other concepts:

// Applicative
template <> struct hana::lift_impl<burrito_tag> {
  template <typename X> static constexpr auto apply(X &&x) {
    // Wrap up the ingredients into a tortilla to make a burrito.
    return burrito<std::decay_t<X>>(std::forward<X>(x));
  }
};

template <> struct hana::ap_impl<burrito_tag> {
  template <typename F, typename X>
  static constexpr decltype(auto) apply(F &&f, X &&x) {
    auto unwrapped_f = std::get<0>(f.get_fillings());
    return hana::transform(x, unwrapped_f);
  }
};

// Monad
template <> struct hana::flatten_impl<burrito_tag> {
  template <typename... Burritos>
  static constexpr decltype(auto) flatten_helper(Burritos &&... bs) {
    return std::tuple_cat(unwrap_fillings(bs)...);
  }

  template <typename... Xs>
  static constexpr decltype(auto) apply(burrito<Xs...> &&xs) {
    // xs is a burrito containing other burritos.
    // We want to unwrap the tortillas and merge the ingredients into one
    // burrito with one tortilla.
    return make_burrito(flatten_helper(std::forward<burrito<Xs...>>(xs)));
  }
};

Note that our implementation of “ap”, ap_impl::apply makes a lot of assumptions. I confess I didn’t totally understand the requirements of the concept in Hana, so I assumed that the wrapped function argument passed to “ap” is not variadic, i.e. it contains only one function “ingredient”.

You can view and run the whole implementation on Wandbox.

Maybe you don’t like Mexican food (you’re wrong–but that’s an argument for another article). Or maybe you aren’t convinced that these ideas are useful in practice. Consider another idea: monadic error handling.

In the burrito example, we define operations that check if the input to the operation is valid. If the input to fry isn’t a food, we simply an empty burrito (the “identity burrito”) to calling context. Any subsequent operations on the nothing burrito will result in a burrito with nothing in it.

    std::string bar = "A bar is not edible.";
    auto result = burrito_pipeline(bar);
    static_assert(std::is_same_v<decltype(result), burrito<>>);

Thus, we have sprinkled static polymorphism into our burritos. With generic lambdas (C++14) and if constexpr (C++17) this pattern is syntactically elegant, and since the check happens at compile time, we should expect it to have a lower runtime cost than throwing an exception or returning and handling error codes upon receiving an invalid input.

The idea of an “empty burrito” as an error state is where the burrito metaphor starts to break down. This pattern makes a lot more sense with a wrapper type called “expected” or “outcome” or even “future”.

Additionally, this burrito example doesn’t really illustrate the full power of monadic error handling. If you’re curious about the topic, this article provides an interesting implementation that uses string concatenation as an example and a monadic “error box” type. I think this pattern is worth revisiting with post-C++14 syntax.

Now that you’re familiar with functors, applicatives, and monads, here’s a question: are there any useful functors that aren’t monads? Monads and applicatives are more powerful than functors and provide more guarantees.

The motivation for this question from my pedantic perspective is: if all the functors we care about using are also monads, then maybe it’s fine if we continue using the name “functor” for function objects.

This article gives a high-level, category-theoretic answer to the question. But we asked for a useful example of a non-monad, non-applicative functor, that we can use in C++ code.

hana::transform probably reminds you of std::transform from the standard algorithms library.

This is what the two function signatures look like:

template<typename Functor, typename UnaryOperation>
auto hana::transform(Functor&& xs, UnaryOperation&& f);

template<typename InputIt, typename OutputIt, typename UnaryOperation>
OutputIt std::transform(InputIt first1, InputIt last1, OutputIt d_first,
                   UnaryOperation unary_op);

Both functions apply UnaryOperation over the stuff contained in the other argument(s). The difference is: in Hana, the Functor class provides the logic for mapping over its contents in a generic fashion, whereas in the standard library, the iterator interface provides this logic. My conclusion is thus that in the C++ standard library, all iterable data structures are also Functors. But they aren’t necessarily monads: for example, std::vector provides no built-in utilities for flattening or composing vectors.

If you understand the idea of functors, you may say, “you’re totally wrong! Function objects are functors!” And you’d be correct. The “transform” operator in this case is simply function composition. But not all functors are function objects, which is why I’m still in favor of use the more specific term.

I’ll end with one last observation about how C++ programmers discuss (or rather, don’t discuss) categories.

Bjarne Stroustrup gave a lightning talk at Meeting C++ on how to design good concepts, in anticipation of their acceptance into the ISO C++ standard.

I find it surprising that Bjarne, who is a huge proponent of concepts, didn’t mention anything about using category theory concepts in this talk, since we have evidence of their usage in another programming language. Maybe he felt that a lightning talk wasn’t long enough to explain and motivate monads, in addition to the spiel about concepts. Or, maybe he, like many others in the C++ community, doesn’t think that monads and functors belong in our standard vocabulary.

Regardless, we don’t need to wait for concepts to become a core language feature to start experimenting with the ideas in this article. If functional programming idioms and using concepts to express categories are new to you, I encourage you to play around with the ideas on your own and decide for yourself if they are worth using.