2017-07-31 58 views
0

我正在为一个以随机顺序返回自然类型值的系统(有些可以是int,其他float,其他字符串[很好,几乎很自然])写一个客户端。问题是,我不知道在编译时会有什么类型的值。类型不可知的getter方法

因为我不知道在查询远程系统之前要返回的值的类型,提供统一接口的最好方法是什么,它允许客户端库的用户提取值正确的类型?

如果查询远程系统返回一个字符串,我想我的get_value()返回一个字符串。如果一个int,使它返回一个int。或者,如何让客户端库使用正确的类型调用getter?

我想模板的类型提示将是一个很好的方法来实现这一目标?

+0

如何从远程系统获取值?是收到的数据中收到的价值类型? – bracco23

+0

我得到他们作为一个字节数组和类型确实带有值。 – ruipacheco

+0

您能提供一个您将从远程系统收到的原始数据的例子吗? – bracco23

回答

3

有两种不同的用例。如果客户端程序可以预先知道它想要的值的类型,则可以为每种可能的类型使用不同的getter(例如,使用getIntgetDoublegetString)或使用模板getter(现代C++方式):

template <class T> 
T get(char *byte_array) { 
    T value; 
    # manage to extract the value 
    return T; 
} 

并明确地实例化它们以确保它们可用。

在客户端库,使用将是:

int i = get<int>(byte_array); 

如果客户端程序可以是在编译​​时不明的顺序接收到的数据,你必须找到一个方法来返回变种数据类型(旧的Basic程序员记得那个)。您可以在升压或C++ 17的实现,但是一个简单的实现可能是:

struct variant { 
    enum Type { INT, DOUBLE, STRING, ... } type; 
    union { 
     int int_val; 
     double d_val; 
     std::string str_val; 
     ... 
    }; 
}; 

在这种情况下,客户端程序将使用

variant v = get(byte_array); 
switch v.type { 
case INT: 
    ... 
} 
+0

所有评论都很棒,但对这篇文章的第一个建议更适合我的用例。 – ruipacheco

6

如果存在支持类型的有限列表,则检查boost或std变体。

如果不是有限的列表,boost或std any(或包含any的变体)。

你也可以找到其他的实现。标准版本在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)); 
    } 
}; 

Live example

转换为C++ 11将包含一堆替换带助手的lambdas。我不喜欢用C++ 11编写,而这个C++ 14是远离它的大多数机械转换。

原因很简单,因为visit只需要一个变体并返回无效等等原因。

代码几乎完全未经测试,但设计很完善。

+0

我是在C++ 11上。标准中的任何东西都适用于它? – ruipacheco

+1

@ruip no。使用boost变体,或者找到一个C++ 11等价物。 – Yakk

+0

@ruipacheco如果您正在使用最近的GCC版本,您可以使用它。 – user0042

2

我对HDF5库有这个完全相同的问题。文件中数据集的类型可以是任何本机类型(现在忽略结构)。我的解决办法如下:

  1. 创建一个抽象基类
  2. 创建从抽象类,其中类型是运行时类型需要
  3. 建立在基类的静态方法派生的模板类它会从你的系统中读取这个类型,然后决定实例化什么。

例如:

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... 
    } 
} 

这方面有很多优点,包括你可以做一个基类的方法获取字符串值,为您的所有类型,并使用所有的酷std::to_string类型。如果你需要做一些特定类型的事情,你只需要专业化。

1

你说你工作在C++ 11所以如果您不想使用Boost作为Variant类型,那么如果返回类型是有限的一组类型,则可以使用标准C-Style union

如果你想要一个变量,不受限制,返回类型,那么你可能会想看看“基于概念的多态性”或“类型擦除”设计模式。

这也是值得研究'模板专业化',它不会有任何用处,除非你知道返回类型时调用,但它是一个很好的技巧,以获得具有相同签名的特定类型处理程序。

+0

从技术上说是UB,但是可行......应该使用reinterpret_cast代替.. – Swift