Reputation: 31110
I am working with a third party library that passes values using a custom vector and variant types. These vectors however map 1:1 to my functions. My end goal is to reduce the amount of boilerplate.
A contrived example:
#include <functional>
// A third party type I can't change
struct dumb_varient_type
{
int to_int() { return 3; }
double to_double() { return 3.14; }
std::string to_string() { return "Pi"; }
};
template< class R, class... Args >
R do_callback(std::function<R(Args...)> func, std::vector<dumb_varient_type> &args )
{
// call func, with the correct types
return func( vector_of_pain_to_expanded_parameter_pack_or_something(args) );
}
int main(int argc, char **argv) {
std::vector<dumb_varient_type> arg1 = get_args_from_magic();
auto c1 = [](int, double) {/*do work*/};
auto c2 = [](double, std::string) {/*do work*/};
auto val1 = do_callback( c1, arg1 );
auto val2 = do_callback( c2, arg1 );
// std::vector< dumb_varient_type > arg2( /* Anything*/ );
// std::function<int(/*Anything*/)> c3;
// auto val3 = do_callback( c3, arg2 );
}
I don't need to worry about ambiguity; The argument list does not need to be routed or dispatched to the correct function. I know the intended callback. But as the argument list is created at runtime there could theoretically be a mismatch in arg count or type.
How would one implement do_callback
(or equivalent)
Upvotes: 2
Views: 691
Reputation: 275405
// A third party type I can't change
struct dumb_varient_type {
int to_int() { return 3; }
double to_double() { return 3.14; }
std::string to_string() { return "Pi"; }
};
Write a tag helper:
template<class T>struct tag_t{using type=T;};
template<class T>constexpr tag_t<T> tag{};
Then in the namespace of tag_t
or dumb_variant_type
write these overloads: (note: not templates, just overloads)
int to_type( tag_t<int>, dumb_variant_type const& dumb ) { return dumb.to_int(); }
double to_type( tag_t<double>, dumb_variant_type const& dumb ) { return dumb.to_double(); }
etc. This makes converting your dumb variant more uniform in generic code.
Now we write indexer:
template<std::size_t...Is>
auto indexer(std::index_sequence<Is...>){
return [](auto&&f){ return f(std::integral_constant<std::size_t,Is>{}... ); };
}
template<std::size_t N>
auto indexer_upto(std::integral_constant<std::size_t,N> ={}){
return indexer(std::make_index_sequence<N>{});
}
this helper does away with having to split your function up. It gives us access to an unpacked pack of indexes at-will.
A call to indexer returns a lambda, which in turn takes a lambda provided by the client. The client lambda is called with an unpacked compile-time parameter pack.
Using this, do_callback
is short and sweet:
template< class R, class... Args >
R do_callback(std::function<R(Args...)> func, std::vector<dumb_varient_type> &args )
{
if(sizeof...(Args) > args.size()) throw std::invalid_argument(); // or whatever
return indexer_upto< sizeof...(Args) >()( []( auto...Is ){
return func( to_type(tag<Args>, args[Is]) ... );
});
}
and done. Please excuse any tpyos.
Upvotes: 4
Reputation: 620
What you need is some way to implicitly convert a dumb_varient_type
into the correct type for the function. Then you need a way to unpack the vector arguments into the function call itself. The later is incredibly dangerous as you do not know the size of the vector at compile time and therefore cannot guarantee that there will always be enough elements to match the number of parameters in the function.
A simple and naive approach for the first step is to create a wrapper class with the correct conversion operators then convert the vector of dumb_varient_type
into smarter_dumb_variant_type
.
struct smarter_dumb_variant_type {
smarter_dumb_variant_type(dumb_varient_type& dvt)
: value(dvt)
{ }
operator ()(int) {
return value.to_int();
}
//... rest of the operators
dumb_varient_type& value;
};
std::vector<smarter_dumb_variant_type> Convert(std::vector<dumb_varient_type>&) {
//Implerment...
}
For the second part, you need to at compile time select then number of elements out of the vector you need.This is why it is dangerous, the vector must have equal or more elements then specified otherwise it will cause undefined behaviour.
A solution to unpack a vector can be found in this question here (but use smarter_dumb_variant_type
instead of int)
Using this, your do_callback will look something like:
template< class R, class... Args, size_t num_args>
R do_callback(std::function<R(Args...)> func, std::vector<dumb_varient_type> &args )
{
unpack_caller<num_args> caller;
return caller(func, Convert(args));
}
If throwing an exception is permissible if there is a size/count mismatch you may consider adding this to the start of the function to prevent undefined behaviour.
if (args.size() < num_args) {
throw;
}
Upvotes: 2