2017-07-31 20 views
0

自然な型の値をランダムな順序で返すシステムのクライアントを書いています(int型でもfloat型でも、他のものでもstring [well、almost natural])。問題は、コンパイル時に値の型がわからないことです。型不自然なゲッターメソッド

リモートシステムが照会されるまで返される値の型がわからないので、クライアントライブラリのユーザーが次の値を抽出するための統一されたインターフェイスを提供する最良の方法は何ですか。正しいタイプ?

リモートシステムを照会すると文字列が返される場合は、get_value()に文字列を返すようにしてください。 intの場合、intを返します。あるいは、正しいタイプのゲッターをクライアント・ライブラリーに呼び出す方法はありますか?

これを実現するには、タイプヒントのテンプレートが適していると思いますか?

+0

リモートシステムからどのように値を取得しますか?あなたが受け取ったデータのどこかに書かれた値のタイプですか? – bracco23

+0

私はそれらをバイト配列として取得し、型に値が付いています。 – ruipacheco

+0

リモートシステムから受信する生データの例を提供できますか? – bracco23

答えて

3

2つの異なるユースケースがあります。クライアントプログラムが必要とする値のタイプを事前に知ることができるなら、可能なタイプごとに異なるゲッターを使用することができます(例えば、getIntgetDoublegetStringなどの良い古いCメソッド)、またはテンプレートゲッター方法:)

と明示的にそれらが利用可能になるようにinstanciate。クライアントライブラリで

、使用量は次のようになります。

int i = get<int>(byte_array); 

クライアントプログラムは、コンパイル時にunknowであるために、データを受信する場合は、バリアントデータを返す方法を見つける必要があります(古いBasicプログラマはそれを覚えています)。あなたはブーストで実装またはC++ 17を見つけることができますが、些細な実装は次のようになります。

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

その場合には、クライアント・プログラムは、あなたはとてもC++ 11で働いていたと述べ

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

すべてのコメントは素晴らしかったですが、この記事の最初の提案は私のユースケースによく合いました。 – ruipacheco

6

サポートされているタイプの有限リストがある場合は、boostまたはstdバリアントを調べてください。

有限リストでない場合は、任意の(またはanyを含むバリアント)を押し上げるかstdします。

他の実装も見つけることができます。標準バージョンはC++ 17です。

簡略版の変種は、おそらく100行または2行のコードで記述することができます。

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++ 14変異体です。

C++ 11への変換は、ラムダをヘルパーに置き換えることで構成されます。私はC++ 11で書くのが好きではありません。このC++ 14は、ほとんどが機械的な変換です。

visitはまったく変種をとり、他の理由の中でもとりわけ無効を返します。

コードはほとんど完全にテストされていませんが、デザインは健全です。

+0

私はC++ 11で。それで動作するstdの何か? – ruipacheco

+1

@ruip no。ブーストバリアントを使用するか、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

を使用します。あなたがVariant型のためにBoostを使いたくないなら、戻り値の型が限定された型のセットなら、標準のC-Style unionを使うことができます。

可変で無制限の戻り値の型が必要な場合は、「概念ベースの多態性」または「タイプ消去」デザインパターンを調べることをお勧めします。

「Template Specialization」も調べる価値がありますが、呼び出し時に戻り値の型がわからない限りは使用できませんが、同じシグネチャで特定の型ハンドラを取得するのは良い方法です。

+0

技術的にはUBですが、実行可能です...代わりにreinterpret_castを使用してください。 – Swift

関連する問題