Search code examples
c++11constructorstd-function

Initializing class with a lambda passed into constructor, C++11


Lets consider following example:

#include <functional>
#include <iostream>

using namespace std;

class Caller {
 public:
  Caller(function<void()> callback) {
    callback();
  }
};

main() {
#if defined(ONELINER)
  Caller caller = [] { cout << "test"; };
#else
  function<void()> fun = [] { cout << "test"; };
  Caller caller(fun);
#endif  // defined(ONELINER)
}

If we simply try to compile it (with -std=c++11 flag) it will happily finish, and display test when run. However if we define ONELINER macro compilation will fail with:

prog.cpp: In function 'int main()':
prog.cpp:17:40: error: conversion from 'main()::<lambda()>' to non-scalar type 'Caller' requested
   Caller caller = [] { cout << "test"; };

I understand that this is caused by the fact that there is implicit conversion from lambda to std::function and then implicit conversion from std::function to Caller, and we cannot perform 2 conversions at the same time.

Is it somehow possible to make syntax Class object = lambda; work? I'm asking because I played recently with writing my own small testing framework for educational reasons and I thought that this:

UNIT_TEST(test_name) {
  // test content
};

is much more elegant than

UNIT_TEST_BEGIN(test_name)
  // unit test
UNIT_TEST_END()

The former can be achieved with lambdas passed into the UnitTest constructor. But with problem that I described I had to use dirty workaround like:

#define UNIT_TEST(test_name) \
    ::std::function<void(::Helper*)> test_name_helper; \
    ::UnitTest test_name ## _test = \
        test_name_helper = \
        [&] (::Helper* helper)

and it doesn't look elegant at all. But even if this can be done without lambdas I'm still intrigued whether Class object = lamda; syntax can be achieved.


Solution

  • Modify the constructor as such:

    template<typename CB>
    Caller(CB callback) {
      callback();
    }
    

    That will allow it to accept any callable argument, be it a lambda, a std::function, function pointer or functor.

    Unfortunately the constructor will also accept any other type as well, but give a compiler error when callback can't be "called" like a function.