Skip to content

Callable Objects: Part 3

Nathan Crookston edited this page Aug 15, 2014 · 1 revision

####Exercise 1: All lambda functions may be directly translated into a functor. While lambda functions offer a more concise syntax, that same syntax may be harder to understand than the more verbose functor. The first exercise hopes to connect the two.

Examine the following lambda:

[&d, i](double lhs, int rhs) -> double
{
  d += i;
  return lhs + rhs + d;
}

It references a double from the scope where the lambda is declared, and copies an int from the same scope.

The following struct shows a possible translation:

struct match_lambda
{
  match_lambda(double& d, int i) : d(d), i(i) {}

  double operator()(double lhs, int rhs)
  {
    d += i;
    return lhs + rhs + d;
  }

  double& d;
  const int i;
};

Question 1:

Because d was captured by reference, it was modifiable. Why can't you modify i? How would you change the lambda to allow it?

Answer 1:

Note that match_lambda::i is of type const int by default. This means that not only will trying to modify i not change the variable in the outer scope, attempts to modify it inside the lambda won't compile.

[i](int val) { i += val; } //ERROR: Cannot modify i.

This was done to avoid surprising the unwary programmer who might think that modifying a variable captured from an outer scope should always result in changing that variable. If you really want to modify variables captured by value, you may declare the lambda mutable:

[i](int val) mutable { i += val; } //OKAY: This still won't change i in the outer scope, though.

This makes the following change to match_lambda above:

  double& d;
-  const int i;
+  int i;
};

A bonus example -- note the returned value of f versus i:

  int i = 3;
  auto f = [=]() mutable -> int
  {
    i += 2;
    return i;
  };

  std::cout << i << std::endl;   // 3
  std::cout << f() << std::endl; // 5
  std::cout << f() << std::endl; // 7
  std::cout << i << std::endl;   // 3

####Exercise 2/Question 2: You can create and use a lambda function at local scope. Since the type of a lambda is unspeakable, we can either use auto to deduce the lambda type or std::function to erase its type. Which of the following would be faster?

auto af = [](int i) { return i * 2; };
std::function<int(int)> sf = [](int i) { return i * 2; };

####Answer 2: In my timings, the auto-stored lambda is approximately 3x faster than the std::function-stored lambda. There is an extra level of indirection involved when using std::function. The cost is reasonable and the uses of std::function many. By and large there's no reason to use std::function for lambdas used only in the local scope [^1] -- it's more useful for the cases we'll discuss later.


####Exercise 3/Question 3: If a lambda (or any callable object, really) is being passed to another function, what is the speed difference between having that function be templatized and having that function accept a std::function?

[](int i) { return std::sqrt(static_cast<double>(i)); }

####Answer 3: The speed difference is more significant. In my tests, the templatized function ran nearly 11x faster. This is why standard algorithms like accumulate and transform are templatized on the function:

template <typename InputIterator, typename OutputType, typename Function>
OutputType accumulate(InputIterator begin, InputIterator end, OutputType initial, Function f);

There are, however, good reasons to use std::function when passing a callable object. For one, it's not possible[^2] to instantiate a templatized function whose declaration is in a header file and whose definition is in a separate cpp file. So using std::function can reduce dependencies. Furthermore, a function which accepts std::function isn't instantiated (at least) once for every different argument -- there is only one definition. This may impact compile and link times. Also, std::function clearly indicates the expected form of the callable object to execute, which template types (currently) do not. Another reason to use std::function may be seen in part 4 below.


####Exercise 4/Question 4:

std::function<void(int)> return_callable() { return [](int i) { std::cout << i << std::endl; }; }

Given that std::function and other type-erased methods are the only generic ways to return a callable object, how would you go about returning such an object if genericity weren't required and std::function were unacceptable? ####Answer 4: You could obviously just return the callable entity itself, assuming it isn't a C++ lambda function. Therefore, functors may be returned by name, functions may be returned by function pointer. Whether you use std::function or not, be sure not to reference variables which will go out of scope in the object you're returning!


####Exercise 5: Note the following objects which must be notified of ongoing key presses:

class character_displayer
{
public:
  void display_character(char c) const
  { std::cout << "Key pressed: " << c << std::endl; }
};

class signal_character_tester
{
public:
  void indicate_if_value(char c) const
  {
    if(c == 'Y')
      std::cout << "Y has been pressed." << std::endl;
  }
};

keyboard_inputter is tightly coupled to character_displayer and signal_character_tester. We expect that more objects will need to be made aware of key presses in the future, and we're unhappy with the number of changes required to keyboard_inputter:

class keyboard_inputter
{
public:
  keyboard_inputter(
    const character_displayer& cd, const signal_character_tester& sct)
    : cd_(cd), sct_(sct)
  {}

  char operator()()
  {
    char c;
    std::cin >> c;

    //Notify the objects that need to hear about this.
    cd_.display_character(c);
    sct_.indicate_if_value(c);
    return c;
  }

private:
  const character_displayer& cd_;
  const signal_character_tester& sct_;
};

void get_input()
{
  character_displayer cd;
  signal_character_tester sct;
  keyboard_inputter ki(cd, sct);
  char c = 0;
  while(c != 'N')
  {
    c = ki();
  }
}

Change keyboard_inputter, character_displayer, signal_character_tester and get_input to use std::function to decouple keyboard_inputter from knowledge of the other classes.

The changes needed are actually pretty simple:

class keyboard_inputter
{
public:
  typedef std::function<void(char)> key_listener;
 
  char operator()()
  {
    char c;
    std::cin >> c;
 
    //Notify the objects that need to hear about this.
    std::for_each(cbs_.begin(), cbs_.end(), [c](key_listener cb) { cb(c); });

    return c;
  }

  void add_key_listener(key_listener cb)
  { cbs_.push_back(cb); }
 private:
  std::vector<key_listner> cbs_;
 };
 
void get_input()
{
  character_displayer cd;
  signal_character_tester sct;
  keyboard_inputter ki;
  ki.add_key_listener([&cd](char c) { cd.display_character(c); });
  ki.add_key_listener([&sct](char c) { sct.indicate_if_value(c); });
  ...

This gives the ability to register arbitrary handlers without modifying keyboard_inputter. This pattern[^3] is so common that several libraries support it. For a case like the previous, Boost.Signals may be used to simplify the implementation and make it more powerful. Here's a potential implementation of keyboard_inputter using it:

class keyboard_inputter
{
  typedef boost::signal<void(char)> key_signal;
public:
  char operator()()
  {
    char c;
    std::cin >> c;
 
    //Notify the objects that need to hear about this.
    key_signal_(c);

    return c;
  }

  boost::signals::connection add_key_listener(key_signal::slot_function_type cb)
  { return key_signal_.connect(cb); }

 private:
  key_signal key_signal_;
 };

In addition to a slight typing savings, it is possible to use the returned connection to temporarily or permanently disconnect a listener [^4].

Hopefully this gives you a good basis for implementing command patterns in modern C++. If you've never used the pattern, you'll likely find that it can simplify and improve your code.


Notes:

[^1] One case is when trying to call a lambda recursively. Another is when you wish to choose between two lambdas:

std::function<void(int)> f;
if(...)
  f = [&](...){branch 1};
else
  f = [&](...){branch 2};

Both cases are pretty uncommon, and you should probably do the former without lambdas anyway.

[^2] There are some limited & occasionally useful circumstances when function template declarations may be separated from their definitions. I won't go into them now, but it's mostly about reducing compile and link times.

[^3] wikipedia has a nice introduction, languages like node.js and frameworks like the C++ Rest SDK also use it extensively.

[^4] For more information, see signals 1 and signals 2

Clone this wiki locally