我正在构建一个复杂的可扩展系统。细节并不重要,但我真的很喜欢这个设计,除了这个问题。
我有一个接口(interface) 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 问题之前:更高层次的抽象不关心具体的状态类型,而我想以尽可能多的类型安全来实现较低的部分。到目前为止,这种方法看起来不错我的需求)
我可以通过将我的元组转换为 vector 来做到这一点,应用 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(); });
}
但是,这仍然缺少std::any_cast
s 到具体类型。
有什么想法吗?
最佳答案
主要问题是您的元组包含 std::shared_ptr<WithState<StateTypes>>...
这就是apply
将尝试调用您给定的 lambda,但 lambda 只需要 StateTypes&&...
.
还有一些改变可以让它工作,但首先是工作:
我改变了
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
和 back 只是不必要的开销 - 如果有的话,它是 less 类型安全的。您的 lambda 参数是
StateTypes&&... withStates
.除了类型本身需要不同之外,&&
除非您实际向 lambda 提供临时变量(std::apply
不会这样做),否则不起作用,或者如果您通过auto&&
进行类型推导,则不起作用(这是不同的)。在代码中,lambda 表达式是这样的(为简单起见,我按值取值,您可以按 [const] 引用取值):[this](std::shared_ptr<WithState<StateTypes>>... withStatePtrs) { handleStates(getStateFor<StateTypes>(withStatePtrs)...); }
您也不需要
dynamic_pointer_cast
从std::shared_ptr<WithState<T>>
至std::shared_ptr<Subject>
.您的元组类型有一个额外的
>
.
编辑:使用更新后的问题中的代码段,您会得到:
相同的注意事项仍然适用,您只需要输入 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>
.
关于c++ - 将函数应用于元组中的每个元素,将每个元素转换为类型包中的不同类型,然后作为参数包传递,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58630183/