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

У меня есть вопрос о сохранении объекта с лямбда-шаблоном в качестве члена класса. Класс 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;
}

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
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, почему-то у него constthis при инициализации 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)};

Демо


Другое решение — использовать вместо этого статическую функцию-член. Используя статическую функцию, вам больше не нужно будет передавать указатель this, и вы можете инициализировать invoker напрямую с помощью &Worker::some_callback:

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

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