How to handle api that returns different data types for the same input types?

How to handle api that returns different data types for the same input types?

In the example below, apicallshould return a date or string depending on the input attribute:

#include <iostream>
#include <string>

using namespace std;

???? apicall(string datatype, string attribute)
{
    // code
}

int main(int argc, char** argv)
{
    string datatype = "Thomas"
    string attribute = "bithday"
    cout << apicall(datatype, attribute) << endl;

    string datatype = "Thomas"
    string attribute = "address"
    cout << apicall(datatype, attribute) << endl;
}

What could be instead of ????( apicallreturn datatype) and how to handle these cases?

I am trying to understand these concepts, as my experience today is related to scripting languages duck typed.

+4
source share
4 answers

The ideal solution is to use std::variantwhich is a safe type of union type.

This allows you to write the following:

using DateOrString = std::variant<DateType, std::string>;

DateOrString api_call(std::string, std::string) {
   // you can return both DateType and std::string
}

// ...
auto result = api_call("", "");
auto& str = std::get<std::string>(result);

, std::variant - ++ 17. .

, boost variant, ++.


"" , , . .

, .

, , .

, , :

auto result = api_call(/*...*/);
if (result.is_string()) {
   // result is a string
   const auto& str = result.get_string();
} else {
   // result is a date
   const auto& date = result.get_date();
}
+6

... ?

. ( ) .

:

  • , , std:: variant ++ 17, Boost.Variant, .
  • ( )
  • , , :

    struct APIHandler {
      virtual ~APIHandler() {}
      virtual void operator()(int) {}
      virtual void operator()(string) {}
    };
    void apicall(string name, string attr, APIHandler &h) {
      // dummy implementation
      if (attr == "address") {
        h("123 Woodford Road");
      } else if (attr == "birthday") {
        h(19830214);
      }
    }
    
    // implement your type-specific logic here
    struct MyHandler: APIHandler {
      void operator()(int i) override {
        cout << "got an int:" << i << '\n';
      }
      void operator()(string s) override {
        cout << "got a string:" << s << '\n';
      }
    };
    
    // and use it like:
    MyHandler mh;
    apicall("Thomas", "birthday", mh);
    apicall("Thomas", "address", mh);
    
+6

, -, . Boost : Boost.Variant Boost.Any.

+2
source

You want std::variantin C ++ 17, or boost::varianteither collapse your own crude version like this:

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));
  }
};

then your code looks like this:

variant<string, int> apicall(string datatype, string attribute)
{
  if (datatype > attribute) return string("hello world");
  return 7;
}

int main()
{
  string datatype = "Thomas"
  string attribute = "bithday"
  apicall(datatype, attribute).visit([](auto&&r){
    cout << r << endl;
  });
  string datatype = "Thomas"
  string attribute = "address"
  apicall(datatype, attribute).visit([](auto&& r){
    cout << r << endl;
  });
}

with any visitor apply_visitorfree functions or methods supported by yours variant.

This gets a lot more annoying in C ++ 11 as we have no common lambdas.

+2
source

Source: https://habr.com/ru/post/1682772/


All Articles