1

I'm writing a client for a system that returns values of natural types in random order (some can be int, others float, others string [well, almost natural]). The problem is, I don't know what type a value will be at compile time.

Since I don't know the type of the value to be returned until after the remote system has been queried, what is the best way to provide a uniform interface that allows a user of the client library to extract the value in the right type?

If querying the remote system once returns a string, I'd like my get_value() to return a string. If an int, make it return an int. Alternatively, how to have the client library call the getter with the right type?

I guess templates with type hinting would be a good way to achieve this?

ruipacheco
  • 15,025
  • 19
  • 82
  • 138

4 Answers4

6

Examine boost or std variant if there is a finite list of supported types.

If not a finite list, boost or std any (or a variant containing an any).

You can find other implementations as well. The std versions are in C++17.

A simplified version of variant could probably be written in a 100 or two lines of code.

Here is a crude C++14 variant:

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.

Converting to C++11 will consist of a bunch of replacing lambdas with helpers. I don't like writing in C++11, and this C++14 is a mostly mechanical transformations away from it.

It is crude, in that visit takes exactly one variant and returns void, among other reasons.

Code is almost completely untested, but the design is sound.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
4

There are two different use case. If the client program can know in advance the type of the value it wants, you can either use a different getter for each possible type (the good old C way with for example getInt, getDouble, getString), or use templated getters (modern C++ way):

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

and explictely instanciate them to make sure that they will be available.

In the client library, the usage will be:

int i = get<int>(byte_array);

If the client program may received data in an order which is unknow at compile time, you must find a way to return a variant data type (old Basic programmers remember that). You can find implementations in boost or C++ 17, but a trivial implementation could be:

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

In that case the client program will use

variant v = get(byte_array);
switch v.type {
case INT:
    ...
}
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
2

I had this exact same problem with the HDF5 library. The type of a dataset from a file can be any native type (ignoring structs for now). My solution was the following:

  1. Create an abstract base class
  2. Create a template class that derives from the abstract class, where the type is the runtime type you need
  3. Create static methods in the base class that will read the type from your system, and then decide what to instantiate.

For example:

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

There are many advantages of this, including that you could make a base-class method that gets the string value for all your types, and use the cool std::to_string for all types. You'll only need specializations if you need to do something that is type specific.

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
1

You said you were working in C++11 so if you don't want to use Boost for it's Variant type then you can use a standard C-Style union if the return type is a limited set of types.

If you want a variable, unrestricted, return type then you will probably want to look into 'Concept Based Polymorphism' or 'Type Erasure' design patters.

It's also worth looking into 'Template Specialisation', it won't be any use unless you know the return type when calling but it's a good trick to get specific type handlers with the same signature.

Treebeard
  • 322
  • 1
  • 9