将函数应用于元组中的每个元素,将每个元素转换为类型包中的不同类型,然后作为参数包传递

Apply function to each element in tuple, cast each to a different type in a type pack, then pass as parameter pack

我正在构建一个复杂的可扩展系统。细节并不重要,但我真的很喜欢这个设计,除了这个问题。

我有一个用于某种类型 T 的接口 WithState<T> : Subject

有问题的 class 是在 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(); });
}

on godbolt

但是,这仍然缺少具体类型的 std::any_cast

有什么想法吗?

主要问题是你的元组包含 std::shared_ptr<WithState<StateTypes>>... 所以这就是 apply 将尝试调用你给定的 lambda,但 lambda 只需要 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(或提供 returns std::any 的非模板化重载)。但是出于这里的目的,转换为 std::any 和返回只是不必要的开销——如果有的话,它是 less 类型安全的。

  • 您的 lambda 参数是 StateTypes&&... withStates。除了类型本身需要不同之外,&& 不起作用,除非您实际向 lambda 提供临时变量(std::apply 不会这样做),或者如果您通过 [= 进行类型推导23=](这是不同的)。在代码中,lambda 表达式是这样的(为了简单起见,我按值取值,您可以按 [const] 引用取值):

        [this](std::shared_ptr<WithState<StateTypes>>... withStatePtrs) {
            handleStates(getStateFor<StateTypes>(withStatePtrs)...);
        }
    
  • 您也不需要 dynamic_pointer_caststd::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>.