Инициализация класса с лямбдой, переданной в конструктор, C++11

Рассмотрим следующий пример:

#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)
}

Если мы просто попытаемся скомпилировать его (с флагом -std=c++11), он благополучно завершится и отобразит test при запуске. Однако, если мы определим компиляцию макроса ONELINER, произойдет сбой:

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"; };

Я так понимаю, что это вызвано тем, что идет неявное преобразование из лямбды в std::function и затем неявное преобразование из std::function в Caller, и мы не можем выполнять 2 преобразования одновременно.

Можно ли как-то заставить работать синтаксис Class object = lambda;? Я спрашиваю, потому что недавно я играл с написанием своей собственной небольшой среды тестирования по образовательным причинам, и я подумал, что это:

UNIT_TEST(test_name) {
  // test content
};

намного элегантнее, чем

UNIT_TEST_BEGIN(test_name)
  // unit test
UNIT_TEST_END()

Первое может быть достигнуто с помощью лямбда-выражений, переданных в конструктор UnitTest. Но с проблемой, которую я описал, мне пришлось использовать грязный обходной путь, например:

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

и вообще не выглядит элегантно. Но даже если это можно сделать без лямбда-выражений, я все еще заинтригован, можно ли добиться синтаксиса Class object = lamda;.


person Mateusz Kubuszok    schedule 28.01.2015    source источник
comment
Большинство стандартных функций с вызываемым аргументом используют шаблоны.   -  person Some programmer dude    schedule 28.01.2015
comment
Не могли бы вы немного уточнить? std::function в конце концов является шаблоном, так что я думаю, вы имеете в виду какой-то конкретный вариант использования?   -  person Mateusz Kubuszok    schedule 28.01.2015


Ответы (1)


Измените конструктор как таковой:

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

Это позволит ему принимать любой вызываемый аргумент, будь то лямбда, std::function, указатель на функцию или функтор.

К сожалению, конструктор также примет любой другой тип, но выдаст ошибку компилятора, когда callback нельзя "вызвать" как функцию.

person Some programmer dude    schedule 28.01.2015
comment
Великолепно! Это так просто, что я чувствую себя дураком. - person Mateusz Kubuszok; 28.01.2015
comment
На заметку: быстрый поиск показал, что проблема принятия всего может быть решена с помощью правильных статических утверждений (stackoverflow.com/questions/ 22882170/). - person Mateusz Kubuszok; 28.01.2015
comment
TBH, если callback используется в списке инициализаторов для инициализации std::function< >, вы сразу же получите разумную ошибку. Для этого вам не нужно дополнительное статическое утверждение. - person MSalters; 28.01.2015