C++ Fusion运行时开关
我正在从文件中读取对象的类型:C++ Fusion运行时开关,c++,metaprogramming,boost-fusion,C++,Metaprogramming,Boost Fusion,我正在从文件中读取对象的类型: enum class type_index { ... }; type_index typeidx = read(file_handle, type_index{}); 根据类型索引的不同,我希望创建一个类型(从可能的类型列表中),并对其执行一些泛型操作(每个类型的泛型代码相同): std::元组可能的类型; boost::fusion::for_each(可能的_类型,[&](自动i){ 如果(i::typeidx!=typeidx){return;} //和
enum class type_index { ... };
type_index typeidx = read(file_handle, type_index{});
根据类型索引的不同,我希望创建一个类型(从可能的类型列表中),并对其执行一些泛型操作(每个类型的泛型代码相同):
std::元组可能的类型;
boost::fusion::for_each(可能的_类型,[&](自动i){
如果(i::typeidx!=typeidx){return;}
//和我一起做一般的事情
});
即:
- 对于不同的类型,我有相同的通用代码
- 我希望编译器为每种类型生成特定的代码
- 我只知道运行时需要哪种类型
- 我只想执行单一类型的代码
switch
语句,但是“cases”是在编译时生成的。特别是,对于每个语句,这一点都不像是一个(我没有对向量、元组、列表中的所有元素做任何事情,而只是对单个元素)
有没有更好更清晰的方法来表达/写这个成语?(例如,对于可能的类型,使用mpl::vector
而不是std::tuple
,对于每个算法,…)使用不同于的。我认为您现有的解决方案不错。在//do generic stuff
时,调用类型重载的其他函数
boost::fusion::for_each(possible_types, [&](auto i) {
if (i::typeidx != typeidx) { return; }
doSpecificStuff(i);
});
void doSpecificStuff(const TypeA& a) { ... }
void doSpecificStuff(const TypeB& b) { ... }
...
好吧,你不可能得到一个开关
,它比这里的if…else
结构要快一点,但不是实质性的,而且对于你在读取文件时运行的进程来说,它不太可能被注意到
其他选项都与此类似。Fusion或mpl随机访问容器,甚至std::tuple都可以通过get进行访问,但这需要编译时索引,因此您正在构建案例,并且仍然使用类似于
if (idx == 0) { doSpecificStuff(std::get<0>(possible_types)); }
else if (idx == 1) ...
....
if(idx==0){doSpecificStuff(std::get(可能的_类型));}
否则如果(idx==1)。。。
....
这可以通过递归模板完成,如:
template <size_t current>
void dispatchImpl(size_t idx)
{
if (idx >= std::tuple_size<possible_types>::value) return;
if (idx == current)
{
doSpecificStuff(std::get<current>(possible_types));
return;
}
dispatchImpl<current + 1>(idx);
}
void dispatch(size_t idx) { dispatchImpl<0>(idx); }
模板
void dispatchImpl(大小\u t idx)
{
if(idx>=std::tuple\u size::value)返回;
如果(idx==当前)
{
doSpecificStuff(标准::获取(可能的_类型));
返回;
}
dispatchImpl(idx);
}
无效调度(size_t idx){dispatchImpl(idx);}
我知道的唯一替代方法是构建一个函数指针数组。看见我不认为你真的能从你的案例中获得任何解决方案,而且更难遵循
您的fusion::for_每个解决方案的一个优点是它不会强制您的类型索引是连续的。随着应用程序的发展,您可以轻松地添加新类型或删除旧类型,代码仍然可以工作,如果您尝试使用容器索引作为类型索引,这将更加困难。当您说我对不同类型有相同的通用代码时;是否可以用相同的原型将其全部封装到一个函数中
如果是这样,您可以使用std::function
映射每个type\u索引
,以使编译器为每个类型生成代码,并有一种简单的方法来调用每个函数来替换开关
开关更换:
function_map.at(read())();
运行示例:
#include <stdexcept>
#include <map>
#include <string>
#include <functional>
#include <iostream>
template<typename Type>
void doGenericStuff() {
std::cout << typeid(Type).name() << std::endl;
// ...
}
class A {};
class B {};
enum class type_index {typeA, typeB};
const std::map<type_index, std::function<void()>> function_map {
{type_index::typeA, doGenericStuff<A>},
{type_index::typeB, doGenericStuff<B>},
};
type_index read(void) {
int i;
std::cin >> i;
return type_index(i);
}
int main(void) {
function_map.at(read())(); // you must handle a possible std::out_of_range exception
return 0;
}
#包括
#包括
#包括
#包括
#包括
模板
void doGenericStuff(){
std::cout i;
返回类型_指数(i);
}
内部主(空){
函数_map.at(read())();//必须处理可能的std::out_of_range异常
返回0;
}
我认为最好的方法就是使用一组函数来完成您想要做的事情:
typedef std::tuple<type1, type2, ..., typeN> PossibleTypes;
typedef std::function<void()> Callback;
PossibleTypes possible_types;
std::array<Callback, std::tuple_size<PossibleTypes >::value> callbacks = {
[&]{ doSomethingWith(std::get<0>(possible_types)); },
[&]{ doSomethingElseWith(std::get<1>(possible_types)); },
...
};
虽然通过std::function
可以实现间接寻址,但在性能上无法真正击败数组查找。这肯定会击败每个解决方案的融合,因为即使有idx==0
,您实际上还是在运行每个元素。在这种情况下,您确实需要使用any()
,这样您就可以提前退出。但使用数组的速度更快 我喜欢我惯常的继承兰姆达斯技巧:
我以前写过这个
- (在这里它访问了一个
boost::variant
的成员)
我相信我见过Sumant Tambe在他最近的cpptruths.com
帖子中使用它
示范
这里有一个演示。稍后将添加一些解释
应用的最重要技巧是使用boost::variant
为我们隐藏类型代码denum。但即使您保留自己的类型识别逻辑(只需要更多编码),该原则也适用
#include <boost/serialization/variant.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <fstream>
#include <iostream>
using namespace boost; // brevity
//////////////////
// This is the utility part that I had created in earlier answers:
namespace util {
template<typename T, class...Fs> struct visitor_t;
template<typename T, class F1, class...Fs>
struct visitor_t<T, F1, Fs...> : F1, visitor_t<T, Fs...>::type {
typedef visitor_t type;
visitor_t(F1 head, Fs...tail) : F1(head), visitor_t<T, Fs...>::type(tail...) {}
using F1::operator();
using visitor_t<T, Fs...>::type::operator();
};
template<typename T, class F> struct visitor_t<T, F> : F, boost::static_visitor<T> {
typedef visitor_t type;
visitor_t(F f) : F(f) {}
using F::operator();
};
template<typename T=void, class...Fs>
typename visitor_t<T, Fs...>::type make_visitor(Fs...x) { return {x...}; }
}
using util::make_visitor;
namespace my_types {
//////////////////
// fake types for demo only
struct A1 {
std::string data;
};
struct A2 {
double data;
};
struct A3 {
std::vector<int> data;
};
// some operations defined on A1,A2...
template <typename A> static inline void serialize(A& ar, A1& a, unsigned) { ar & a.data; } // using boost serialization for brevity
template <typename A> static inline void serialize(A& ar, A2& a, unsigned) { ar & a.data; } // using boost serialization for brevity
template <typename A> static inline void serialize(A& ar, A3& a, unsigned) { ar & a.data; } // using boost serialization for brevity
static inline void display(std::ostream& os, A3 const& a3) { os << "display A3: " << a3.data.size() << " elements\n"; }
template <typename T> static inline void display(std::ostream& os, T const& an) { os << "display A1 or A2: " << an.data << "\n"; }
//////////////////
// our variant logic
using AnyA = variant<A1,A2,A3>;
//////////////////
// test data setup
AnyA generate() { // generate a random A1,A2...
switch (rand()%3) {
case 0: return A1{ "data is a string here" };
case 1: return A2{ 42 };
case 2: return A3{ { 1,2,3,4,5,6,7,8,9,10 } };
default: throw std::invalid_argument("rand");
}
}
}
using my_types::AnyA;
void write_archive(std::string const& fname) // write a test archive of 10 random AnyA
{
std::vector<AnyA> As;
std::generate_n(back_inserter(As), 10, my_types::generate);
std::ofstream ofs(fname, std::ios::binary);
archive::text_oarchive oa(ofs);
oa << As;
}
//////////////////
// logic under test
template <typename F>
void process_archive(std::string const& fname, F process) // reads a archive of AnyA and calls the processing function on it
{
std::ifstream ifs(fname, std::ios::binary);
archive::text_iarchive ia(ifs);
std::vector<AnyA> As;
ia >> As;
for(auto& a : As)
apply_visitor(process, a);
}
int main() {
srand(time(0));
write_archive("archive.txt");
// the following is c++11/c++1y lambda shorthand for entirely compiletime
// generated code for the specific type(s) received
auto visitor = make_visitor(
[](my_types::A2& a3) {
std::cout << "Skipping A2 items, just because we can\n";
display(std::cout, a3);
},
[](auto& other) {
std::cout << "Processing (other)\n";
display(std::cout, other);
}
);
process_archive("archive.txt", visitor);
}
从类型索引
到处理代码,构建一个无序映射
阅读类型索引
,在地图中查找,执行。错误检查是否缺少条目
简单、可扩展、可版本化——只需在条目上添加一个长度头(确保它处理64位长度——最大较低的位计数长度意味着下一个是真实长度,这允许单位长度开始),如果您不理解条目,可以跳过它。您的示例提醒我建议的(我相信已拒绝)()。我认为它的功能与您想要的类似,但我可能误解了。@cv_和_he:Boost.Switch是一个很好的想法。注意:图书馆没有被拒绝,但是:-你可能想考虑把它作为一个答案。<代码> Type索引< /Cl>连续并从零开始吗?它听起来像是你想要的正是一个Booost。变量提供,加上一个工厂函数,它最初填充了这个变体。但是这是基于类型的区分,而OP只有在运行时才知道的值,不是吗?@ IdjjARNMMM.看来你是对的。我已经让自己过多地扫描了现有的答案。修复…@ildjarn已经更新了演示代码,使其更加相关。我稍后会解释-但我现在必须运行:)啊,我明白了;我怀疑这就是你的方向。+1,这是
template <typename... T, size_t... Is>
std::array<Callback, sizeof...(T)> makeCallbacksImpl(std::tuple<T...>& t,
integer_sequence<Is...>)
{
return { [&]{ doSomethingWith(std::get<Is>(t)) }... };
// or maybe if you want doSomethingWith<4>(std::get<4>(t)):
// return { [&]{ doSomethingWith<Is>(std::get<Is>(t)) }... };
}
template <typename... T>
std::array<Callback, sizeof...(T)> makeCallbacks(std::tuple<T...>& t) {
return makeCallbacksImpl(t, make_integer_sequence<sizeof...(T)>{});
}
void genericStuffWithIdx(int idx) {
if (idx >= 0 && idx < callbacks.size()) {
callbacks[idx]();
}
else {
// some error handler
}
}
void genericStuffWithIdx(int idx) {
callbacks.at(idx)(); // could throw std::out_of_range
}
#include <boost/serialization/variant.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <fstream>
#include <iostream>
using namespace boost; // brevity
//////////////////
// This is the utility part that I had created in earlier answers:
namespace util {
template<typename T, class...Fs> struct visitor_t;
template<typename T, class F1, class...Fs>
struct visitor_t<T, F1, Fs...> : F1, visitor_t<T, Fs...>::type {
typedef visitor_t type;
visitor_t(F1 head, Fs...tail) : F1(head), visitor_t<T, Fs...>::type(tail...) {}
using F1::operator();
using visitor_t<T, Fs...>::type::operator();
};
template<typename T, class F> struct visitor_t<T, F> : F, boost::static_visitor<T> {
typedef visitor_t type;
visitor_t(F f) : F(f) {}
using F::operator();
};
template<typename T=void, class...Fs>
typename visitor_t<T, Fs...>::type make_visitor(Fs...x) { return {x...}; }
}
using util::make_visitor;
namespace my_types {
//////////////////
// fake types for demo only
struct A1 {
std::string data;
};
struct A2 {
double data;
};
struct A3 {
std::vector<int> data;
};
// some operations defined on A1,A2...
template <typename A> static inline void serialize(A& ar, A1& a, unsigned) { ar & a.data; } // using boost serialization for brevity
template <typename A> static inline void serialize(A& ar, A2& a, unsigned) { ar & a.data; } // using boost serialization for brevity
template <typename A> static inline void serialize(A& ar, A3& a, unsigned) { ar & a.data; } // using boost serialization for brevity
static inline void display(std::ostream& os, A3 const& a3) { os << "display A3: " << a3.data.size() << " elements\n"; }
template <typename T> static inline void display(std::ostream& os, T const& an) { os << "display A1 or A2: " << an.data << "\n"; }
//////////////////
// our variant logic
using AnyA = variant<A1,A2,A3>;
//////////////////
// test data setup
AnyA generate() { // generate a random A1,A2...
switch (rand()%3) {
case 0: return A1{ "data is a string here" };
case 1: return A2{ 42 };
case 2: return A3{ { 1,2,3,4,5,6,7,8,9,10 } };
default: throw std::invalid_argument("rand");
}
}
}
using my_types::AnyA;
void write_archive(std::string const& fname) // write a test archive of 10 random AnyA
{
std::vector<AnyA> As;
std::generate_n(back_inserter(As), 10, my_types::generate);
std::ofstream ofs(fname, std::ios::binary);
archive::text_oarchive oa(ofs);
oa << As;
}
//////////////////
// logic under test
template <typename F>
void process_archive(std::string const& fname, F process) // reads a archive of AnyA and calls the processing function on it
{
std::ifstream ifs(fname, std::ios::binary);
archive::text_iarchive ia(ifs);
std::vector<AnyA> As;
ia >> As;
for(auto& a : As)
apply_visitor(process, a);
}
int main() {
srand(time(0));
write_archive("archive.txt");
// the following is c++11/c++1y lambda shorthand for entirely compiletime
// generated code for the specific type(s) received
auto visitor = make_visitor(
[](my_types::A2& a3) {
std::cout << "Skipping A2 items, just because we can\n";
display(std::cout, a3);
},
[](auto& other) {
std::cout << "Processing (other)\n";
display(std::cout, other);
}
);
process_archive("archive.txt", visitor);
}
Processing (other)
display A3: 10 elements
Skipping A2 items, just because we can
display A1 or A2: 42
Processing (other)
display A1 or A2: data is a string here
Processing (other)
display A3: 10 elements
Processing (other)
display A1 or A2: data is a string here
Processing (other)
display A1 or A2: data is a string here
Processing (other)
display A3: 10 elements
Processing (other)
display A1 or A2: data is a string here
Processing (other)
display A3: 10 elements
Processing (other)
display A3: 10 elements