Лямбда высокого порядка С ++ не может вывести возвращаемый тип лямбда
У меня есть задание создать практически универсальный класс, который получает какой-то входной источник целых чисел и какой-то потребитель, который потребляет эти входные данные. Мне это удалось, но это уродливее, чем мне хотелось бы. Так что это часть моего решения, которое работает.
Это интерфейс, который реализуют мои источники ввода:
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
- просто сделайте обычную функцию - возможно, некоторых это смущает.