在 C++ 中,如何创建包含可变模板对象的异构向量?
In C++, how can I create a heterogenous vector containing variafic templated objects?
嗨 Whosebug 社区!
我在工作中使用了可变参数模板、继承和抽象工厂模式,现在正在努力使它们协同工作。看来我已经达到了我目前对这些主题的了解,所以如果你能给我一个提示或代码示例,我将非常感激!提前致谢 ;)
这是上下文(我很抱歉!有几行代码...):
我有一个 Base class
template<typename... Params>
class P
{
public:
virtual void compute(Params&... ps) = 0;
// other things ...
};
和衍生 classes
template<typename... Params>
class AP : public P<Params...>
{
public:
void compute(Params&... ps) override { _compute(std::forward<Params&>(ps)...); }
private:
void _compute(std::string& str) {std::cout << "AP::compute str " << str << std::endl;}
};
using A = AP<std::string>;
template<typename... Params>
class BP : public P<Params...>
{
public:
void compute(Params&... ps) override { _compute(std::forward<Params&>(ps)...); }
private:
void _compute(int& i) {std::cout << "BP::compute i " << i << std::endl;}
};
using B = BP<int>;
到这里,没问题!
如果我做一个小 main()
,这没有任何问题:
int main()
{
std::unique_ptr<P<int>> p1 = std::make_unique<B>();
int i = 15;
p1->compute(i);
std::unique_ptr<P<std::string>> p2 = std::make_unique<A>();
std::string str = "abc";
p2->compute(str);
}
但是,我们可以添加更多:工厂的 Base classes。
(这些将与我的 class P 以外的其他 classes 一起使用...如果您想知道为什么 :) )
template<typename Base>
class Creator
{
public:
virtual std::unique_ptr<Base> create() = 0;
};
template<class Key, class T>
class Factory
{
public:
void store(Key key, std::unique_ptr<Creator<T>>&& creator)
{
_crs[key] = std::move(creator);
}
std::unique_ptr<T> create(Key key)
{
return _crs[key]->create();
}
private:
std::map<Key, std::unique_ptr<Creator<T>>> _crs;
};
及其实现能够构建 P 相关对象:
template<typename Derived, typename... Params>
class PCreator : public Creator<P<Params...>>
{
public:
std::unique_ptr<P<Params...>> create() override
{
return std::make_unique<Derived>();
}
};
template<typename... Params>
class PFactory : public Factory<std::string, P<Params...>>
{
public:
PFactory()
{
this->store("int", std::make_unique<PCreator<BP<int>>>);
this->store("string", std::make_unique<PCreator<AP<std::string>>>);
}
// create() and store() methods inherited
};
如果我实例化 PFactory,编译器显然不能完成它的工作,因为它需要 PFactory 的模板参数,这会将它们转发给Factory<std::string, P<Params...>>
.
但是,我的工厂只能创建一个 "type" of P 对象,可以使用这些 Params
的对象。
这是我一个人走的路(遗憾的是我的同事都没有能力帮助我...)
我的目标是能够写出这样的东西:
class Thing
{
const std::array<std::string, 2> a = {"one", "two"};
public:
Thing()
{
PFactory f;
for(const auto& e : a)
_ps[e] = std::move(f.create(e));
}
void compute()
{
int i = 100;
std::string str = "qwerty";
// additional computations...
_ps["one"]->compute(i);
// additional computations...
_ps["two"]->compute(str);
}
private:
std::map<std::string, std::unique_ptr<P>> _ps;
};
这是我尝试在 CompilerExplorer 上工作和返工的 PoC,上面的来源是从哪里来的。
任何帮助将不胜感激!
[编辑] 是的,我妄想我可以欺骗编译器来创建带有运行时信息的各种方法签名。
解决方案总结:
(@walnut: thanks!) let compute take std::any or something like that
我不太了解 std::any
,但在 rtfm-ing CppReference 之后,它可以完成这项工作,接受这个事实,我需要将参数转换回我需要的参数它在我的 Derived classes 中(并处理异常)。
可悲的是,在实际项目中,compute()
可以采用多个参数(我使用可变参数模板的原因......我不想关心每个 [=21= 中参数的数量或类型] 方法在每个 Derived class) 中,所以它会迫使我创建 compute(const std::any&)
和 compute(const std::any&, const std::any&)
,等等
(@MaxLanghof: thanks!) One (ugly) solution is to provide all possible compute overloads as virtual functions by hand.
是的,你说得对,我也觉得很奇怪(我不会说到“丑陋”,但我还没有更漂亮的解决方案,所以...),但它正在运行。
这里的缺点是我无法将 class P(和相关的 classes)存储在它自己的库中开始分离关注点(«MainProgram» 玩 Ps 派生自 lib::P)。
(@MaxLanghof: thanks!) to do the entire _ps mapping at compile-time.
我还没有足够的 C++ 经验和知识来实现这样的事情。我需要努力解决这个问题,如果有人有具体的 link(我的意思是:不是 Google 上的第一个 link ;))或示例,我很乐意学习。
感谢您到目前为止的回答!
[编辑] 嗨!抱歉耽搁了,我刚刚回到这个项目,非常感谢你的想法和经验!这意义重大!
我在@Caleth 和@KonstantinStupnik 的工作中做了一些工作(非常感谢您的示例:它们帮助我理解了我在做什么!)并通过我的测试达到了这一点案例:https://gcc.godbolt.org/z/AJ8Lsm 我遇到了 std::bad_any_cast
异常,但不明白为什么...
我怀疑引用传递或我使用 lambda 在 std::any
中保存 compute
方法的方式有问题,但不能确定。
我试图扩展 AnyCallable<void>::operator()
中接收到的类型,以发现与 P 的构造函数中存储函数的区别,但对我来说似乎是一样的。
我试图将 &AP::compute
传递给 P 的构造函数,但是编译器无法再推断出参数类型...
感谢大家的宝贵时间、帮助和建议!
您可以对参数进行类型擦除,只要您可以在调用站点指定它们即可。
至少:
#include <functional>
#include <any>
#include <map>
#include <iostream>
template<typename Ret>
struct AnyCallable
{
AnyCallable() {}
template<typename F>
AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
template<typename ... Args>
AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
template<typename ... Args>
Ret operator()(Args&& ... args)
{
return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...);
}
template<typename ... Args>
Ret compute(Args ... args)
{
return operator()(std::forward<Args>(args)...);
}
std::any m_any;
};
template<>
struct AnyCallable<void>
{
AnyCallable() {}
template<typename F>
AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
template<typename ... Args>
AnyCallable(std::function<void(Args...)> fun) : m_any(fun) {}
template<typename ... Args>
void operator()(Args&& ... args)
{
std::invoke(std::any_cast<std::function<void(Args...)>>(m_any), std::forward<Args>(args)...);
}
template<typename ... Args>
void compute(Args ... args)
{
operator()(std::forward<Args>(args)...);
}
std::any m_any;
};
using P = AnyCallable<void>;
void A(std::string& str) {std::cout << "AP::compute i " << str << std::endl;}
void B(int i) {std::cout << "BP::compute i " << i << std::endl;}
class Thing
{
public:
Thing(){}
void compute()
{
int i = 100;
std::string str = "qwerty";
// additional computations...
ps["one"].compute<int>(i);
// additional computations...
ps["two"].compute<std::string&>(str);
}
private:
std::map<std::string, P> ps = { { "one", B }, { "two", A } };
};
嗨 Whosebug 社区!
我在工作中使用了可变参数模板、继承和抽象工厂模式,现在正在努力使它们协同工作。看来我已经达到了我目前对这些主题的了解,所以如果你能给我一个提示或代码示例,我将非常感激!提前致谢 ;)
这是上下文(我很抱歉!有几行代码...):
我有一个 Base class
template<typename... Params>
class P
{
public:
virtual void compute(Params&... ps) = 0;
// other things ...
};
和衍生 classes
template<typename... Params>
class AP : public P<Params...>
{
public:
void compute(Params&... ps) override { _compute(std::forward<Params&>(ps)...); }
private:
void _compute(std::string& str) {std::cout << "AP::compute str " << str << std::endl;}
};
using A = AP<std::string>;
template<typename... Params>
class BP : public P<Params...>
{
public:
void compute(Params&... ps) override { _compute(std::forward<Params&>(ps)...); }
private:
void _compute(int& i) {std::cout << "BP::compute i " << i << std::endl;}
};
using B = BP<int>;
到这里,没问题!
如果我做一个小 main()
,这没有任何问题:
int main()
{
std::unique_ptr<P<int>> p1 = std::make_unique<B>();
int i = 15;
p1->compute(i);
std::unique_ptr<P<std::string>> p2 = std::make_unique<A>();
std::string str = "abc";
p2->compute(str);
}
但是,我们可以添加更多:工厂的 Base classes。 (这些将与我的 class P 以外的其他 classes 一起使用...如果您想知道为什么 :) )
template<typename Base>
class Creator
{
public:
virtual std::unique_ptr<Base> create() = 0;
};
template<class Key, class T>
class Factory
{
public:
void store(Key key, std::unique_ptr<Creator<T>>&& creator)
{
_crs[key] = std::move(creator);
}
std::unique_ptr<T> create(Key key)
{
return _crs[key]->create();
}
private:
std::map<Key, std::unique_ptr<Creator<T>>> _crs;
};
及其实现能够构建 P 相关对象:
template<typename Derived, typename... Params>
class PCreator : public Creator<P<Params...>>
{
public:
std::unique_ptr<P<Params...>> create() override
{
return std::make_unique<Derived>();
}
};
template<typename... Params>
class PFactory : public Factory<std::string, P<Params...>>
{
public:
PFactory()
{
this->store("int", std::make_unique<PCreator<BP<int>>>);
this->store("string", std::make_unique<PCreator<AP<std::string>>>);
}
// create() and store() methods inherited
};
如果我实例化 PFactory,编译器显然不能完成它的工作,因为它需要 PFactory 的模板参数,这会将它们转发给Factory<std::string, P<Params...>>
.
但是,我的工厂只能创建一个 "type" of P 对象,可以使用这些 Params
的对象。
这是我一个人走的路(遗憾的是我的同事都没有能力帮助我...)
我的目标是能够写出这样的东西:
class Thing
{
const std::array<std::string, 2> a = {"one", "two"};
public:
Thing()
{
PFactory f;
for(const auto& e : a)
_ps[e] = std::move(f.create(e));
}
void compute()
{
int i = 100;
std::string str = "qwerty";
// additional computations...
_ps["one"]->compute(i);
// additional computations...
_ps["two"]->compute(str);
}
private:
std::map<std::string, std::unique_ptr<P>> _ps;
};
这是我尝试在 CompilerExplorer 上工作和返工的 PoC,上面的来源是从哪里来的。
任何帮助将不胜感激!
[编辑] 是的,我妄想我可以欺骗编译器来创建带有运行时信息的各种方法签名。
解决方案总结:
(@walnut: thanks!) let compute take std::any or something like that
我不太了解 std::any
,但在 rtfm-ing CppReference 之后,它可以完成这项工作,接受这个事实,我需要将参数转换回我需要的参数它在我的 Derived classes 中(并处理异常)。
可悲的是,在实际项目中,compute()
可以采用多个参数(我使用可变参数模板的原因......我不想关心每个 [=21= 中参数的数量或类型] 方法在每个 Derived class) 中,所以它会迫使我创建 compute(const std::any&)
和 compute(const std::any&, const std::any&)
,等等
(@MaxLanghof: thanks!) One (ugly) solution is to provide all possible compute overloads as virtual functions by hand.
是的,你说得对,我也觉得很奇怪(我不会说到“丑陋”,但我还没有更漂亮的解决方案,所以...),但它正在运行。 这里的缺点是我无法将 class P(和相关的 classes)存储在它自己的库中开始分离关注点(«MainProgram» 玩 Ps 派生自 lib::P)。
(@MaxLanghof: thanks!) to do the entire _ps mapping at compile-time.
我还没有足够的 C++ 经验和知识来实现这样的事情。我需要努力解决这个问题,如果有人有具体的 link(我的意思是:不是 Google 上的第一个 link ;))或示例,我很乐意学习。
感谢您到目前为止的回答!
[编辑] 嗨!抱歉耽搁了,我刚刚回到这个项目,非常感谢你的想法和经验!这意义重大!
我在@Caleth 和@KonstantinStupnik 的工作中做了一些工作(非常感谢您的示例:它们帮助我理解了我在做什么!)并通过我的测试达到了这一点案例:https://gcc.godbolt.org/z/AJ8Lsm 我遇到了 std::bad_any_cast
异常,但不明白为什么...
我怀疑引用传递或我使用 lambda 在 std::any
中保存 compute
方法的方式有问题,但不能确定。
我试图扩展 AnyCallable<void>::operator()
中接收到的类型,以发现与 P 的构造函数中存储函数的区别,但对我来说似乎是一样的。
我试图将 &AP::compute
传递给 P 的构造函数,但是编译器无法再推断出参数类型...
感谢大家的宝贵时间、帮助和建议!
您可以对参数进行类型擦除,只要您可以在调用站点指定它们即可。
至少:
#include <functional>
#include <any>
#include <map>
#include <iostream>
template<typename Ret>
struct AnyCallable
{
AnyCallable() {}
template<typename F>
AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
template<typename ... Args>
AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
template<typename ... Args>
Ret operator()(Args&& ... args)
{
return std::invoke(std::any_cast<std::function<Ret(Args...)>>(m_any), std::forward<Args>(args)...);
}
template<typename ... Args>
Ret compute(Args ... args)
{
return operator()(std::forward<Args>(args)...);
}
std::any m_any;
};
template<>
struct AnyCallable<void>
{
AnyCallable() {}
template<typename F>
AnyCallable(F&& fun) : AnyCallable(std::function(fun)) {}
template<typename ... Args>
AnyCallable(std::function<void(Args...)> fun) : m_any(fun) {}
template<typename ... Args>
void operator()(Args&& ... args)
{
std::invoke(std::any_cast<std::function<void(Args...)>>(m_any), std::forward<Args>(args)...);
}
template<typename ... Args>
void compute(Args ... args)
{
operator()(std::forward<Args>(args)...);
}
std::any m_any;
};
using P = AnyCallable<void>;
void A(std::string& str) {std::cout << "AP::compute i " << str << std::endl;}
void B(int i) {std::cout << "BP::compute i " << i << std::endl;}
class Thing
{
public:
Thing(){}
void compute()
{
int i = 100;
std::string str = "qwerty";
// additional computations...
ps["one"].compute<int>(i);
// additional computations...
ps["two"].compute<std::string&>(str);
}
private:
std::map<std::string, P> ps = { { "one", B }, { "two", A } };
};