C ++ лямбда высокого порядка не может вывести возвращаемый лямбда-тип - PullRequest
0 голосов
/ 26 марта 2020

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

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

 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 ::, которая работает с числами, которые даны из входного источника

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 se c и делает это, пока входной источник не выдаст -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, который принимает обычную ссылку на действие, а не ссылку на значение. Как мне грустно, это работает, но я хотел бы удалить это явное создание шаблона. Я не знаю, возможно ли это, но я думаю, что это возможно. Если кто-то может объяснить, почему он не может вывести тип неявным образом, потому что я его не понимаю. Спасибо.

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

1 Ответ

0 голосов
/ 26 марта 2020

Пример методов лямбда-автоматического вывода в 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;
    };
};

Какая-то глупость и неэффективность, но это работает.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...