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)
source to share
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");
source to share
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)) {}
};
source to share