Как вывести аргумент шаблона при сохранении класса с лямбда-шаблоном в качестве члена другого класса?

У меня есть вопрос о сохранении объекта с лямбда-шаблоном в качестве члена класса. Класс Invoker — это шаблонный класс, хранящий произвольную лямбда-функцию. Я хочу сохранить экземпляр Invoker в другом классе, Worker. Однако я не знаю, как заполнить аргумент шаблона TCallback, когда Invoker используется как член класса. Он не выводится, как первая строка в функции main. Как показано в комментариях, я пытаюсь определить лямбду где-то в Worker и передать ее члену типа Invoker.

Что я пробовал, так это использовать decltype метода класса, но его нельзя было вызвать как общую лямбду - для запуска ему нужен контекст объекта класса.

Большое спасибо за любые идеи и, возможно, некоторые обходные пути.

Спасибо.

#include <iostream>

template <typename TCallback>
struct Invoker {
  explicit Invoker(TCallback&& cb) : cb(cb) {}

  TCallback cb;

  void invoke() {
    cb();
  }
};

struct Worker {
  void some_callback() {
    std::cout << "callback in worker\n";
  }

  // Not working: some_callback is a member function and can only be called with the context object
  Invoker<decltype(&Worker::some_callback)> invoker{&Worker::some_callback};

  // How to make a something like this?
//   auto lambda_in_class = [&]{some_callback()};
  // Invoker<decltype(lambda_in_class)> invoker{lambda_in_class};
};

int main() {
  Invoker invoker([]{std::cout << "invoker\n";});
  Worker worker;
  worker.invoker.invoke();
  return 0;
}

Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Калькулятор CGPA 12 для семестра
Калькулятор CGPA 12 для семестра
Чтобы запустить этот код и рассчитать CGPA, необходимо сохранить код как HTML-файл, а затем открыть его в веб-браузере. Для этого выполните следующие...
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
О тренинге HTML JavaScript :HTML (язык гипертекстовой разметки) и CSS (каскадные таблицы стилей) - две основные технологии для создания веб-страниц....
Как собрать/развернуть часть вашего приложения Angular
Как собрать/развернуть часть вашего приложения Angular
Вам когда-нибудь требовалось собрать/развернуть только часть вашего приложения Angular или, возможно, скрыть некоторые маршруты в определенных средах?
Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы
Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем...
Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах
При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после...
0
0
56
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вам действительно нужно использовать ремплейты? Это работает:

#include <iostream>
#include <functional>

struct Invoker {
  explicit Invoker(std::function<void()> cb) : cb(cb) {}

  std::function<void()> cb;

  void invoke() {
    cb();
  }
};

struct Worker {
  void some_callback() {
    std::cout << "callback in worker\n";
  }

   // Not working: some_callback is a member function and can only be called with the context object
   Invoker invoker{std::bind(&Worker::some_callback, this)};

   // How to make a something like this?
   std::function<void()> lambda_in_class = [&]{some_callback();};
   Invoker invoker1{lambda_in_class};
};

int main() {
  Invoker invoker([]{std::cout << "invoker\n";});
  Worker worker;
  worker.invoker.invoke();
  worker.invoker1.invoke();
  return 0;
}

Вы также можете сделать это с шаблонами, но вам понадобится руководство по выводу шаблонов (которое является функцией С++ 17) для лямбда-выражений:

#include <iostream>
#include <functional>

template <typename TCallback>
struct Invoker {
  explicit Invoker(TCallback cb) : cb(cb) {}

  TCallback cb;

  void invoke() {
    cb();
  }
};

template<typename T>
Invoker(T) -> Invoker<T>;

struct Worker {
  void some_callback() {
    std::cout << "callback in worker\n";
  }

  // Not working: some_callback is a member function and can only be called with the context object
  Invoker<decltype(std::bind(&Worker::some_callback, static_cast<Worker *>(0)))> invoker{std::bind(&Worker::some_callback, this)};

  // How to make a something like this?
//   auto lambda_in_class = [&]{some_callback()};
  // Invoker<decltype(lambda_in_class)> invoker{lambda_in_class};
};

int main() {
  Invoker invoker([]{std::cout << "invoker\n";});
  Worker worker;
  worker.invoker.invoke();
  return 0;
}

Лямбда внутри класса будет работать только с std::function, так как невозможно использовать auto для члена класса, и вы не можете определить его тип каким-либо другим способом.

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

  // Add constructor to Invoker definition
  template<typename T>
  Invoker(void (T::*func)(), T* obj) : cb(std::bind(func, obj)) {}

// Add template guide after Invoker definition
template<typename T>
Invoker(void (T::*func)(), T* obj) -> Invoker<decltype(std::bind(func, obj))>;


  // Now you can declare invoker in Worker like this
  decltype(Invoker(&Worker::some_callback, static_cast<Worker *>(0))) invoker{&Worker::some_callback, this};

  // Or outside, like this
  Worker worker;
  auto invoker = Invoker{&Worker::some_callback, &worker};

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

Yuanyi Wu 10.01.2023 20:10

@YuanyiWu Добавил несколько вариантов...

sklott 10.01.2023 21:11

Отличный пример! Два вопроса: 1. код не компилировался в Visual Studio из-за C2665; 2. Я не знаком с руководством по дедукции. Кажется, если я уберу руководство по дедукции template<typename T> Invoker(T) -> Invoker<T>;, оно все равно скомпилируется и запустится. Здесь это требуется?

Yuanyi Wu 11.01.2023 06:02

Еще один вопрос не по теме: кажется, компилятор способен вывести спецификации типов. Есть идеи, почему эта функция не включена для участников класса, и нам приходится писать так много стандартных кодов?

Yuanyi Wu 11.01.2023 06:25

1. Что-то странное происходит с MSVC, почему-то у него const this при инициализации invoker. Таким образом, вы можете либо добавить актерский состав const_cast<Worker *>(this), либо использовать второе руководство по дедукции. 2. Это руководство по дедукции было для внеклассовой лямбды. Без него не получалось.

sklott 11.01.2023 07:39

Любая идея, почему эта функция не включена? Я понятия не имею, но если мне придется угадывать, вероятно, чтобы не вывести неправильный тип из значения по умолчанию...

sklott 11.01.2023 07:40

Проблема с:

Invoker<decltype(&Worker::some_callback)> invoker{&Worker::some_callback};

Заключается в том, что some_callback является нестатической функцией-членом. Когда вы вызываете его, ему фактически передается неявный указатель this. Поэтому, когда вы вызываете его через указатель функции-члена, требуется объект Worker (который не существует в вашем классе Invoker.


Одним из обходных путей является использование выражения привязки для привязки указателя this к some_callback:

std::bind(&Worker::some_callback, this)

Затем тип результата можно преобразовать в std::function<void()> и сохранить в Worker.invoker:

Invoker<std::function<void()>> invoker{std::bind(&Worker::some_callback, this)};

Демо


Invoker<std::function<void()>> invoker{&Worker::some_callback};

Демо

Спасибо! Привязка функции-члена к шаблону вызывающей стороны была лишь одной из моих попыток. Моя цель — привязать произвольную лямбду к шаблону Invoker, а также сохранить Invoker как член (без использования std::function). Как вы думаете, это возможно?

Yuanyi Wu 10.01.2023 20:37

@YuanyiWu Только один тип может быть связан с invoker, поскольку точный тип invoker не может измениться. Чтобы связать несколько типов, лучше всего сделать Worker класс шаблона.

Ranoiaetep 10.01.2023 20:48

@YuanyiWu Также обратите внимание, что, основываясь на вашем Invoker, cb не может принимать никаких аргументов, и любые возвращаемые значения отбрасываются, поэтому любые переданные ему функции фактически являются типом void().

Ranoiaetep 10.01.2023 20:49

Да. Обратный вызов является концептуальным, поэтому его подпись здесь не имеет большого значения. В конце концов, фактический тип лямбды является частным для компилятора.

Yuanyi Wu 10.01.2023 20:53

Что касается предложения сделать класс Worker шаблоном, я на самом деле не хочу привязываться несколько раз к обратному вызову. Я хочу сохранить обратный вызов только один раз (при инициализации, возможно, в конструкторе). Проблема в том, что тип шаблона Invoker не подлежит вычету.

Yuanyi Wu 10.01.2023 20:57

Я ожидаю какой-то псевдокод, например закомментированное лямбда-объявление в примере кода. Это выглядит просто, но кажется очень недостижимым.

Yuanyi Wu 10.01.2023 21:00

@YuanyiWu Проблема с вашим прокомментированным кодом не в том, что тип invoker нельзя вывести, а в том, что у lambda_in_class нет типа. Значение по умолчанию члена не предоставляет информацию о типе самому члену. Он только предоставляет информацию о том, как они инициализируются.

Ranoiaetep 10.01.2023 21:23

@YuanyiWu Вместо auto lambda_in_class = ... вы должны вручную указать конкретный тип, например function<void()>. Демо

Ranoiaetep 10.01.2023 21:24

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