我正在处理一些代码,该代码获取的数据如下所示:
enum data_type { INT16 = 0, INT32, UINT64, FLOAT, TIMESTAMP };
struct buffer {
data_type element_type;
size_t size; // in elements of element_type, not bytes
void* data;
}
(这是简化的;实际上,该结构中还有很多其他类型,更多字段等)
现在,我发现自己在编译时编写了一堆实用程序代码,以将枚举值“转换”为实际类型,反之亦然。然后我意识到我需要做一些事情,我需要在运行时也做同样的事情,并且要使用可变数量的缓冲区...所以现在,除了基于类型特征的值查找和枚举之外,基于模板参数的类型查找-我正在编写查找std::type_info
的代码。有点混乱。
但实际上-我不应该这样做。这是重复的,而且我绝对可以确定自己正在重新发明轮子-实现已经被多次编写的东西:编译器,DBMS,数据文件解析器,序列化库等。
该如何做才能最大程度地减少在此方面浪费的精力?
注意:
foo(buffer* buffers, int num_buffers);
。gsl
,因此您可以根据需要在答案中使用它。至于Boost-在政治上可能很难依靠,但是出于StackOverflow问题的目的,我想很好。答案 0 :(得分:3)
这里的目标应该是尽快回到C ++类型系统。为此,应该有一个中央功能,可以根据(运行时)data_type
进行切换,然后将每种情况移交给(编译时)模板版本。
您尚未说明相关函数的外观,但这是一个示例:
template<typename T>
struct TypedBuffer
{
TypedBuffer(void* data, size_t elementCount) { /* ... */ }
// ...
};
template<typename T>
void handleBufferTyped(void* data, size_t elementCount)
{
TypedBuffer<T> buf(data, elementCount);
// Do whatever you want - you're back in the type system.
}
void handleBuffer(buffer buf)
{
switch (buf.element_type)
{
case INT16: handleBufferTyped<int16_t>(buf.data, buf.size); break;
case INT32: handleBufferTyped<int32_t>(buf.data, buf.size); break;
case UINT64: handleBufferTyped<uint64_t>(buf.data, buf.size); break;
case FLOAT: handleBufferTyped<float>(buf.data, buf.size); break;
case TIMESTAMP: handleBufferTyped<std::time_t>(buf.data, buf.size); break;
}
}
如果需要,您还可以让TypedBuffer
从非模板基类继承,以便可以多态地从handleBuffer
返回,但这混合了很多范例,可能没有必要。
答案 1 :(得分:2)
如何不重新发明轮子?
只需将std::variant
与来回转换一起使用。它在标准库中是有原因的。
在重新发明轮子之前,访问是处理类型擦除数据的最简单的通用机制
enum data_type { INT16 = 0, INT32, UINT64, FLOAT, TIMESTAMP, size };
template<data_type d>
struct data
{
using type = void;
};
template<>
struct data<INT16>
{
using type = int16_t;
};
// and so on
template<data_type d>
using data_t = typename data<d>::type;
template<typename F, typename T>
void indirect(void* f, void* t, int n)
{
(*(F*)f)((T*)t, n);
}
template<typename F, size_t... Is>
void visit_(F&& f, buffer* bufs, int n, std::index_sequence<Is...>)
{
using rF = typename std::remove_reference<F>::type;
using f_t = void(*)(void*, void*, int);
static constexpr f_t fs[] = {indirect<rF, data_t<data_type(Is)>>...};
for(int i = 0; i < n; i++)
fs[bufs[i].element_type](&f, bufs[i].data, bufs[i].size);
}
template<typename F>
void visit(F&& f, buffer* bufs, int n)
{
visit_(std::forward<F>(f), bufs, n, std::make_index_sequence<data_type::size>{});
}
std::index_sequence
和朋友可以在C ++ 11中相对容易地实现。用作
struct printer
{
template<typename T>
void operator()(T* t, int n)
{
for(int i = 0; i < n; i++)
std::cout << t[i] << ' ';
std::cout << '\n';
}
};
void foo()
{
visit(printer{}, nullptr, 0);
}
答案 2 :(得分:1)
这似乎就是type_traits用于(https://en.cppreference.com/w/cpp/types)的原因。
基本上,您定义一个模板化的结构,默认情况下它是空的,并且专门针对您拥有的每个枚举。然后在代码中使用MyTypeTraits<MyEnumValue>::type
来获取与所需枚举关联的类型。
一切都在编译时定义。如果需要运行时信息,则始终可以根据模板的值进行一些调度(例如,如果您还存储枚举)。
答案 3 :(得分:1)
使用boost::variant
和gsl::span
。
enum data_type { INT16 = 0, INT32, UINT64, FLOAT, TIMESTAMP };
struct buffer {
data_type element_type;
size_t size; // in elements of element_type, not bytes
void* data;
};
template<class...Ts>
using var_span = boost::variant< gsl::span< Ts > ... >;
using buffer_span = var_span< std::int16_t, std::int32_t, std::uint64_t, float, ??? >;
buffer_span to_span( buffer buff ) {
switch (buff.element_type) {
case INT16: return gsl::span<std::int16_t>( (std::int16_t*)buff.data, buff.size );
// etc
}
}
现在可以
auto span = to_span( buff );
然后访问跨度以安全地访问数据缓冲区。
在c++14中,由于[](auto&&)
的lambda,写作访客的痛苦较小,但在c++11中是可行的。
写template<class...Fs> struct overloaded
也可以使写访问者更加容易。有许多实现方式。
如果您无法使用boost
,则可以将to_span
转换为visit_span
并吸引访问者。
如果您不能使用gsl
,则编写自己的span
太简单了。
visit_span( buff, overload(
[](span<int16_t> span) { /* code */ },
[](span<int32_t> span) { /* code */ },
// ...
));
或
struct do_foo {
template<class T>
void operator()(span<T> span) { /* code */ }
};
visit_span( buff, do_foo{captures} );