4

I have some 3rdParty library with a method like this:

bool Invoke(const char* method, Value* args, size_t nargs)

It takes an array of its inner type (convertible to any primitive c++ types) and arg count as its inner params. In my code, I wrote some generalized helper to avoid manual creation and type convertion for each invoke:

template<class ... Args>
bool WrappedValue::Invoke(const char* method, Args&& ... args)
{
    3rdParty::Value values[] = 
    {
        3rdParty::Value(std::forward<Args>(args))...
    }

    return m_value.Invoke(method, values, sizeof ... (Args));
}

It works just fine, but now I should have 3rdParty code defined in my header files and lib connected directly to my main project.

Is it possible to hide implementation details and usage of this 3rd party library? (Use some kind of pimple idiom or proxy object for 3rdParty::Value ). I know that it's not possible to use virtual template methods in c++ to create a proxy or simply move template implementation to .cpp, so I am totally stuck with this problem.

Will be grateful for any help)

Michael
  • 145
  • 5

2 Answers2

2

Sure. Simply write the equivalent of std::variant<int, double, char, every, other, primitive, type>.

Now your Invoke converts your args into an array (vector, span, whatever) of those variants.

Then you pass this array of variants to your internal Invoke method.

That internal invoke method then uses the equivalent of std::visit to generate a 3rdParty::Value from each of your variants.

Boost provides a boost::variant which would probably work.

You could also roll this by hand. By narrowly specifying your problem, you'd get away with something simpler than a std::variant. It would be more than a bit of work, however.


Another approach is this

template<class T> struct tag_t {constexpr tag_t(){}; using type=T;};
template<class T> constexpr tag_t<T> tag{};

template<class T, class F, class ... Args>
bool WrappedValue::Invoke(tag_t<T>, F&& f, const char* method, Args&& ... args)
{
  T values[] = {
    T(std::forward<Args>(args))...
  };

  return std::forward<F>(f)(method, values, sizeof...(Args));
}

which is simpler. Here you'd write:

bool r = Invoke( tag<3rdParty::Value>, [&](const char* method, 3rdParty::Value* values, std::size_t count) {
  m_value.Invoke( method, values, count );
}, 3.14, 42, "hello world");
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Your second method exposes the 3rdParty API, i.e. `3rdParty::Value` in the actual `Invoke()` call, which the OP wants to avoid. – Walter May 29 '17 at 20:11
  • @Walter The first method hides the 3rd party API from the caller, the second from the method itself. I was uncertain which the OP wanted; which is the "main project". So I included both options. – Yakk - Adam Nevraumont May 29 '17 at 20:23
1

If you want to avoid exposing the 3rdParty API, you need some non-template method to pass the data. That inevitably would require some type-erasure mechanism (like std::any), which instead is exposed in your API.

So, yes you could do that, but then the 3rdParty Value is already a type erasure method and this would only pass the data from one type erasure to the next, creating additional overhead. Whether that price is worth paying only you can decide.

I somehow overlooked your remark that the arguments are all primitive. In this case, type erasure is much simpler and can be done via a tag+union like

struct erasure_of_primitive
{
  enum { is_void=0, is_str=1, is_int=2, is_flt=3, is_ptr=4 }
  int type = is_void;
  union {
    const char*s;  // pointer to external C-string
    int64_t i;     // any integer
    double d;      // any floating point number
    void*p;        // any pointer
  } u;

  erasure_of_primitive() = default;
  erasure_of_primitive(erasure_of_primitive&const) = default;
  erasure_of_primitive&operator=(erasure_of_primitive&const) = default;

  erasure_of_primitive(const char*str)
  : type(is_str), u.s(str) {}

  template<typename T> 
  erasure_of_primitive(T x, enable_if_t<is_integer<T>::value>* =0)
  : type(is_int), u.i(x) {}

  template<typename T> 
  erasure_of_primitive(T x, enable_if_t<is_floating_point<T>::value>* =0)
  : type(is_flt), u.d(x) {}

  template<typename T> 
  erasure_of_primitive(T*x)
  : type(is_ptr), u.p(static_cast<void*>(x)) {}
};
Walter
  • 44,150
  • 20
  • 113
  • 196