Лямбда высокого порядка С ++ не может вывести возвращаемый тип лямбда

У меня есть задание создать практически универсальный класс, который получает какой-то входной источник целых чисел и какой-то потребитель, который потребляет эти входные данные. Мне это удалось, но это уродливее, чем мне хотелось бы. Так что это часть моего решения, которое работает.

Это интерфейс, который реализуют мои источники ввода:

 class InputSource {
   public:
      virtual const InputSource& operator<<(int& num) const = 0;
      virtual ~InputSource()=default;
 };

Это одна из реализаций, которые вводятся с клавиатуры:

class KeyboardInput : public InputSource {
   public:
      KeyboardInput()=default;
      virtual ~KeyboardInput()=default;

   virtual const InputSource& operator<<(int& num) const override {
      std::cin >> num;
      return *this;
   }
};

И это моя реализация класса числовой последовательности, который принимает некоторый входной источник и выполняет действие, которое представляет собой std::function, которая работает с числами, заданными из входного источника.

class NumberSequence {

const InputSource &input_source;
const std::function<void(int)> &action;

int next_num() {int num; input_source<<num; return (num<0 ? -1 : num);} // -1 if no morenumbers

public:
   NumberSequence(const InputSource &input_source, const std::function<void(int)> &action) : input_source(input_source), action(action) {}

   void start_action() {
       int num;
       do {
          action(num = next_num());
          if(num == -1) break;
          std::this_thread::sleep_for(std::chrono::seconds(1));
       } while(true);
   }
 };

Мясо этого класса - функция-член start_action, которая получает ввод от заданного источника ввода, а затем вызывает действие с этим числом и ждет 1 секунду и делает это, пока источник ввода не даст -1, что очень просто.

Итак, теперь я написал реализацию одного действия, которое выводит эти числа в файл, но не как класс, а как лямбда, например:

static auto write_to_file_action = [](std::ofstream& output_file) {

     return [&output_file](int num){
       if(num == -1) return;
       using namespace std::chrono;

       time_point<system_clock> now = system_clock::now();
       std::time_t time = system_clock::to_time_t(now);
       output_file << num <<"\t"<< std::ctime(&time) << std::endl;
    };
};

Эта лямбда принимает std::ofstream в файл, в который я вывожу числа, плюс добавляет время, но это не совсем актуально (частично со временем). Таким образом, я использую это в моей основной функции примерно так:

int main(void) {
   KeyboardInput input_source;

   std::ofstream output_file("output_file.txt");
   NumberSequence num_seq(input_source, write_to_file_action(output_file));

   num_seq.start_action();

   return 0;
}

Как я уже сказал, это работает, но я хотел бы иметь что-то вроде этого:

int main(void) {
   KeyboardInput input_source;

   NumberSequence num_seq(input_source, write_to_file_action("output_file.txt"));

   num_seq.start_action();

   return 0;
}

Это выглядит так просто, но мне сложно это реализовать. Я пробовал реализовать write_to_file_action следующим образом:

static auto write_to_file_action = [](const char* file_name) {

    std::ofstream output_file(file_name);

    return [output_file = std::move(output_file)](int num) mutable {
       if(num == -1) return;
       using namespace std::chrono;

       time_point<system_clock> now = system_clock::now();
       std::time_t time = system_clock::to_time_t(now);
       output_file << num <<"\t"<< std::ctime(&time) << std::endl;
    };
};

Но затем я получаю ошибку компиляции, которая в основном говорит о том, что это не сработает, потому что мой класс NumberSequence хочет std::function, но std::function должен быть копируемым, и это не мой случай. В закрытии моей внутренней лямбды у меня есть std::ofstream, который нельзя скопировать.

Итак, я попытался создать шаблон своего класса NumberSequence следующим образом:

template<typename Func>
class NumberSequence {

    const InputSource &input_source;
    const Func action;

    int next_num() {int num; input_source<<num; return (num<0 ? -1 : num);} // -1 if no more numbers

    public:
       NumberSequence(const InputSource &input_source, Func &&action) 
           : input_source(input_source), action(std::move(action)) {}

    void start_action() {
        int num;
        do {
           action(num = next_num());
           if(num == -1) break;
           std::this_thread::sleep_for(std::chrono::seconds(1));
        } while(true);
    }
};

Это не будет компилироваться, но теперь оно не компилируется, потому что в нем говорится, что отсутствует аргумент шаблона перед переменной num_seq в main(он может вывести его), поэтому я могу сделать что-то вроде этого:

int main(void) {
    KeyboardInput input_source;

    auto lambda = write_to_file_action("output_file.txt");
    NumberSequence<decltype(lambda)> num_seq(input_source, lambda);

    num_seq.start_action();

    return 0;
}

И мне также нужно создать еще один конструктор в моей NumberSequence, который принимает обычную ссылку на действие, а не ссылку rvalue. Как мне грустно, это работает, но я хотел бы удалить эту явную установку шаблона. Не знаю, возможно ли это, но думаю, что это возможно. Если кто-то может объяснить, почему он не может неявно вывести тип, потому что я его не понимаю. Спасибо.

PS Извините за длинный пост, это мой первый пост, и я хотел охватить весь контекст моей проблемы.

1 ответ

Решение

Пример методов автоматического вывода лямбда в C++14. Один генерирует функцию шаблона, которая определяет тип и возвращает соответственно созданный шаблон класса.

#include <iostream>
#include <memory>

using namespace std;

    template<typename PFunc>
    class   CProcGuard
    {
    public:
        CProcGuard(PFunc&& f):mFunc(std::move(f)){};
        CProcGuard(CProcGuard&&) = default;
        ~CProcGuard()
        {
            mFunc();
        }

    private:
        PFunc mFunc;
    };

    template<typename PFunc>
    auto    ExecuteOnExit(PFunc&& f) -> CProcGuard<PFunc>
    {
        return CProcGuard<PFunc>(std::move(f)); // god bless RVO
    }

int main()
{
    std::unique_ptr<int> nonMovable =make_unique<int>(5);

    auto exitGuard = ExecuteOnExit([nm = std::move(nonMovable)]()
    {
        cout<<"Hello World " << *nm;
    });

    return 0;
}

Второй вариант - сделать лямбду подвижной через std::shared_ptr. Что-то подобное:

static auto write_to_file_action = [](const char* file_name) {

    auto ptr_output_file = std::make_shared<std::ofstream>(file_name);

    return [ptr_output_file = std::move(ptr_output_file)](int num) mutable {
       if(num == -1) return;
       using namespace std::chrono;

       time_point<system_clock> now = system_clock::now();
       std::time_t time = system_clock::to_time_t(now);
       *ptr_output_file  << num <<"\t"<< std::ctime(&time) << std::endl;
    };
};

Довольно тупой и неэффективный, но он работает.

Примечание: нет смысла делать статическую лямбду write_to_file_action - просто сделайте обычную функцию - возможно, некоторых это смущает.

Другие вопросы по тегам