我正在为一个系统编写一个客户端,该系统以随机顺序返回自然类型的值(有些可以是int,有些可以是浮点数,有些则是[很好,几乎是自然的])。问题是,我不知道编译时值是什么类型。
由于我不知道在查询远程系统之前要返回的值的类型,提供统一接口的最佳方法是什么,允许客户端库的用户提取值。对吗?
如果查询远程系统一次返回一个字符串,我希望我的get_value()
返回一个字符串。如果是int,则使其返回int。或者,如何让客户端库使用正确的类型调用getter?
我认为带有类型提示的模板是实现这一目标的好方法吗?
答案 0 :(得分:6)
如果存在受支持类型的有限列表,请检查boost或std变体。
如果不是有限列表,则提升或标准任何(或包含任意的变体)。
您也可以找到其他实现。 std版本在C ++ 17中。
变体的简化版本可能用100行或两行代码编写。
这是粗略的C ++ 14变体:
constexpr std::size_t max() { return 0; }
template<class...Ts>
constexpr std::size_t max( std::size_t t0, Ts...ts ) {
return (t0<max(ts...))?max(ts...):t0;
}
template<class T0, class...Ts>
struct index_of_in;
template<class T0, class...Ts>
struct index_of_in<T0, T0, Ts...>:std::integral_constant<std::size_t, 0> {};
template<class T0, class T1, class...Ts>
struct index_of_in<T0, T1, Ts...>:
std::integral_constant<std::size_t,
index_of_in<T0, Ts...>::value+1
>
{};
struct variant_vtable {
void(*dtor)(void*) = 0;
void(*copy)(void*, void const*) = 0;
void(*move)(void*, void*) = 0;
};
template<class T>
void populate_vtable( variant_vtable* vtable ) {
vtable->dtor = [](void* ptr){ static_cast<T*>(ptr)->~T(); };
vtable->copy = [](void* dest, void const* src){
::new(dest) T(*static_cast<T const*>(src));
};
vtable->move = [](void* dest, void* src){
::new(dest) T(std::move(*static_cast<T*>(src)));
};
}
template<class T>
variant_vtable make_vtable() {
variant_vtable r;
populate_vtable<T>(&r);
return r;
}
template<class T>
variant_vtable const* get_vtable() {
static const variant_vtable table = make_vtable<T>();
return &table;
}
template<class T0, class...Ts>
struct my_variant {
std::size_t index = -1;
variant_vtable const* vtable = 0;
static constexpr auto data_size = max(sizeof(T0),sizeof(Ts)...);
static constexpr auto data_align = max(alignof(T0),alignof(Ts)...);
template<class T>
static constexpr std::size_t index_of() {
return index_of_in<T, T0, Ts...>::value;
}
typename std::aligned_storage< data_size, data_align >::type data;
template<class T>
T* get() {
if (index_of<T>() == index)
return static_cast<T*>((void*)&data);
else
return nullptr;
}
template<class T>
T const* get() const {
return const_cast<my_variant*>(this)->get<T>();
}
template<class F, class R>
using applicator = R(*)(F&&, my_variant*);
template<class T, class F, class R>
static applicator<F, R> get_applicator() {
return [](F&& f, my_variant* ptr)->R {
return std::forward<F>(f)( *ptr->get<T>() );
};
}
template<class F, class R=typename std::result_of<F(T0&)>::type>
R visit( F&& f ) & {
if (index == (std::size_t)-1) throw std::invalid_argument("variant");
static const applicator<F, R> table[] = {
get_applicator<T0, F, R>(),
get_applicator<Ts, F, R>()...
};
return table[index]( std::forward<F>(f), this );
}
template<class F,
class R=typename std::result_of<F(T0 const&)>::type
>
R visit( F&& f ) const& {
return const_cast<my_variant*>(this)->visit(
[&f](auto const& v)->R
{
return std::forward<F>(f)(v);
}
);
}
template<class F,
class R=typename std::result_of<F(T0&&)>::type
>
R visit( F&& f ) && {
return visit( [&f](auto& v)->R {
return std::forward<F>(f)(std::move(v));
} );
}
explicit operator bool() const { return vtable; }
template<class T, class...Args>
void emplace( Args&&...args ) {
clear();
::new( (void*)&data ) T(std::forward<Args>(args)...);
index = index_of<T>();
vtable = get_vtable<T>();
}
void clear() {
if (!vtable) return;
vtable->dtor( &data );
index = -1;
vtable = nullptr;
}
~my_variant() { clear(); }
my_variant() {}
void copy_from( my_variant const& o ) {
if (this == &o) return;
clear();
if (!o.vtable) return;
o.vtable->copy( &data, &o.data );
vtable = o.vtable;
index = o.index;
}
void move_from( my_variant&& o ) {
if (this == &o) return;
clear();
if (!o.vtable) return;
o.vtable->move( &data, &o.data );
vtable = o.vtable;
index = o.index;
}
my_variant( my_variant const& o ) {
copy_from(o);
}
my_variant( my_variant && o ) {
move_from(std::move(o));
}
my_variant& operator=(my_variant const& o) {
copy_from(o);
return *this;
}
my_variant& operator=(my_variant&& o) {
move_from(std::move(o));
return *this;
}
template<class T,
typename std::enable_if<!std::is_same<typename std::decay<T>::type, my_variant>{}, int>::type =0
>
my_variant( T&& t ) {
emplace<typename std::decay<T>::type>(std::forward<T>(t));
}
};
转换为C ++ 11将包含一堆用帮助器替换lambdas。我不喜欢用C ++ 11编写,而这个C ++ 14主要是远离它的机械转换。
这是粗俗的,因为visit
只取一个变量并返回void,以及其他原因。
代码几乎完全未经测试,但设计合理。
答案 1 :(得分:3)
有两种不同的用例。如果客户端程序可以事先知道它想要的值的类型,则可以为每种可能的类型使用不同的getter(例如getInt
,getDouble
,{{ 1}}),或使用模板化的getter(现代C ++方式):
getString
并明确地将它们设置为确保它们可用。
在客户端库中,用法为:
template <class T>
T get(char *byte_array) {
T value;
# manage to extract the value
return T;
}
如果客户端程序可能按照编译时未知的顺序接收数据,则必须找到一种方法来返回 variant 数据类型(旧的Basic程序员记住这一点)。你可以在boost或C ++ 17中找到实现,但是一个简单的实现可能是:
int i = get<int>(byte_array);
在这种情况下,客户端程序将使用
struct variant {
enum Type { INT, DOUBLE, STRING, ... } type;
union {
int int_val;
double d_val;
std::string str_val;
...
};
};
答案 2 :(得分:2)
我在HDF5库中遇到了同样的问题。文件中数据集的类型可以是任何本机类型(暂时忽略结构)。我的解决方案如下:
例如:
static std::shared_ptr<Base> GetVariable()
{
switch(mytype)
{
case INT16:
return std::make_shared<Derived<uint16_t>>(value);
case INT32:
return std::make_shared<Derived<uint32_t>>(value);
//etc...
}
}
这有很多优点,包括你可以创建一个基类方法来获取所有类型的字符串值,并对所有类型使用cool std::to_string
。如果你需要做一些特定类型的事情,你只需要专业化。
答案 3 :(得分:1)
你说你在使用C ++ 11工作,所以如果你不想使用Boost它的Variant类型那么你可以使用标准的C-Style union type是一组有限的类型。
如果你想要一个变量,不受限制的返回类型,那么你可能想要研究基于概念的多态性&#39;或者&#39; Type Erasure&#39;设计模式。
它也值得研究&#39;模板专业化,除非你在通话时知道返回类型,否则它不会有任何用处,但它是一个很好的技巧来获得具体的具有相同签名的类型处理程序。