Hiding the implementation of the variation pattern

I have a 3rdParty library with this way:

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

      

It accepts an array of its internal type (convertible to any C ++ primitive types), and arg is its internal parameters. In my code, I wrote a generic helper to avoid manual creation and type conversion for each call:

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 fine, but I should now have 3rdParty code defined in my header and lib files linked directly to my main project.

Is it possible to hide the implementation details and usage of this third party library? (Use some kind of pimple idiom or proxy for 3rdParty :: Value). I know it is not possible to use virtual template methods in C ++ to create proxies, or just move the template implementation to .cpp, so I am completely stuck with this issue.

We will be grateful for any help)

+3


source to share


2 answers


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

.

Now yours will Invoke

convert yours args

to an array (vector, span, whatever) of these options.

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

This inner call method uses an equivalent std::visit

to generate 3rdParty::Value

from each of your options.

Boost provides boost::variant

, which will probably work.

You can also flip this manually. By narrowly specifying your problem, you get away from something simpler than std::variant

. However, this will be more than a little work.




Another approach is

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 easier. You should write here:

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

      

+2


source


If you want to avoid exposing the 3Party API, you need some kind of non-templated method to pass the data. This will inevitably require some kind of style erasing mechanism (like std::any

) which is instead displayed in your API.

So yes , you can do that, but then 3rdParty is Value

already a type erasure method and it will only transfer data from erasing one type to another, creating additional overhead. Whether the price is worth paying only you can decide.



I somehow missed your remark that arguments are all primitive. In this case, type erasure is much easier and can be done with the + union tag, for example

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

      

+1


source







All Articles