Примените функцию к каждому элементу в кортеже, приведите каждый к другому типу в пакете типов, затем передайте как пакет параметров - PullRequest
1 голос
/ 30 октября 2019

Я строю сложную расширяемую систему. Детали не важны, но мне действительно нравится дизайн, за исключением этого вопроса.

У меня есть интерфейс WithState<T> : Subject для некоторого типа T.

Проблемный класс настроен на template <typename... StateTypes>.

Он содержит std::tuple<std::shared_ptr<WithState<StateTypes>>...>>.

У меня есть функция std::any getStateFor(std::shared_ptr<Subject>) (где WithState<T> : Subject).

У меня также есть функция void handleStates(StateTypes... states) (возможно, какдавайте возьмем кортеж на этом этапе, что бы ни было проще)

Теперь мне нужно соединить все эти части вместе: мне нужно выгрузить элементы в моем кортеже до shared_ptr<Subject>, затем применить getStateFor к каждому изэти элементы по порядку и std::any_cast результаты в порядке StateTypes..., а затем пересылают все это сразу как пакет параметров в handleStates.

(Раньше это обозначалось как проблема XY: более высокие уровни абстракций не заботятся о конкретных типах состояний, тогда как я хочу реализовать нижние части с максимально возможной безопасностью типов. Пока что этот подходвыглядит хорошо для моих нужд)

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

Вот скелет существующего кода:

#include <memory>
#include <tuple>
#include <any>
#include <iostream>
#include <cassert>
#include <functional>

// ignoring references and const for brevity

class Subject { 
public:
  /* deleted copy assignment and constructor */ 
  // this is here so that the example works
  virtual std::any getState() = 0;
};

template <typename T> class WithState : public Subject { };

template <typename... StateTypes>
class StateHandler {
public:   
  std::tuple<std::shared_ptr<WithState<StateTypes>>...> subjects;
  // this one is actually in another class, but it doesn't matter
  void handleStates(StateTypes... states);

  void handleStatesForSubjects(std::function<std::any (std::shared_ptr<Subject>)> getStateFor) {
    // how do I implement this?
  }
};

int main() {
  struct foo { int a; int b; };

  struct WithInt : public WithState<int> { 
    std::any getState() override { return 17; } 
  };
  struct WithFoo : public WithState<foo> {
    std::any getState() override { return foo { 1, 2 }; }
  };

  StateHandler<int, foo> handler;

  handler.subjects = { 
    std::make_shared<WithInt>(), std::make_shared<WithFoo>() };

  handler.handleStatesForSubjects([](auto subj) { return subj->getState(); });
}

на Godbolt

Тем не менее, до сих пор не хватает std::any_cast s для конкретных типов.

Есть идеи?

1 Ответ

1 голос
/ 30 октября 2019

Основная проблема в том, что ваш кортеж содержит std::shared_ptr<WithState<StateTypes>>..., поэтому apply будет пытаться вызвать вашу лямбду, но лямбда принимает только StateTypes&&....

Есть еще несколько измененийчтобы все заработало, но сначала работаем:

https://godbolt.org/z/hyFBtV

  • Я изменил getStateFor на функцию шаблона, где вы указываете ожидаемый тип:

    template<class StateType>
    StateType getStateFor(std::shared_ptr<Subject> s)
    {
        if (auto withState = std::dynamic_pointer_cast<WithState<StateType>>(s))
            return withState->getValue();
        throw "AHHH";
    }
    

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

  • Ваши лямбда-аргументы были StateTypes&&... withStates. Помимо самого типа, который должен отличаться, && не будет работать, если вы фактически не предоставите временные значения лямбде (что не будет делать std::apply), или если вы не сделаете вывод типа через auto&& (которыйразные). В коде лямбда-выражение выглядит следующим образом (я взял значение для простоты, вы можете взять ссылку [const]):

        [this](std::shared_ptr<WithState<StateTypes>>... withStatePtrs) {
            handleStates(getStateFor<StateTypes>(withStatePtrs)...);
        }
    
  • Вам также не нужно dynamic_pointer_cast чтобы перейти от std::shared_ptr<WithState<T>> к std::shared_ptr<Subject>.

  • Ваш тип кортежа имеет дополнительный >.


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

https://godbolt.org/z/D_AJ1n

По-прежнему действуют те же соображения, вам просто нужно добавить туда any_cast:

void handleStatesForSubjects(std::function<std::any(std::shared_ptr<Subject>)> getStateFor)
{
  std::apply(
    [this, getStateFor](std::shared_ptr<WithState<StateTypes>>... withStates) {
      handleStates(std::any_cast<StateTypes>(getStateFor(withStates))...);
    },
    subjects
  );
}

Возможно, это более просто, чем вы ожидали, но вы просто записываете операцию для каждого элемента: вызовите getStateFor, затем any_cast. Повторите для каждого переменного аргумента (...). Вам не нужно dynamic_pointer_cast или подобное - std::shared_ptr<Derived> неявно конвертируется в std::shared_ptr<Base>.

...