我正在使用第三方库,该库使用自定义向量和变体类型传递值。但是,这些向量将1:1映射到我的功能。我的最终目标是减少样板的量。
一个人为的例子:
#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 );
}
我不必担心歧义;不需要将参数列表路由或派遣到正确的功能。我知道预定的回调。但是,由于参数列表是在运行时创建的,因此从理论上讲,ARG计数或键入中可能存在不匹配。
一个人将如何实现 do_callback
(或同等(
// 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 T>struct tag_t{using type=T;};
template<class T>constexpr tag_t<T> tag{};
然后在tag_t
或dumb_variant_type
的命名空间中写下以下超载:(注意:不是模板,只是过载(
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(); }
等。这使得将愚蠢变体转换在通用代码中。
现在我们编写索引:
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>{});
}
这个助手不得不将功能拆分。它使我们可以在Will中访问一包未包装的索引。
索引器的呼叫返回lambda,而lambda又采用了客户提供的lambda。客户lambda带有未包装的编译时参数包。
使用此功能,do_callback
短而甜:
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]) ... );
});
}
完成。请原谅任何tpyos。
您需要的是将dumb_varient_type
隐式转换为该功能的正确类型的某种方法。然后,您需要一种将矢量参数解放到函数调用本身中的方法。以后是非常危险的,因为您不知道编译时向量的大小,因此无法保证将始终有足够的元素匹配函数中的参数数量。
第一步的一种简单而幼稚的方法是使用正确的转换操作员创建一个包装类,然后将dumb_varient_type
的向量转换为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...
}
在第二部分中,您需要在编译时间选择,然后选择所需的向量的元素数量。这就是为什么它很危险,向量必须具有相等或更多元素,然后指定否则将导致不确定的行为。
可以在此处找到解压缩矢量的解决方案(但是使用smarter_dumb_variant_type
而不是INT(
使用此功能,您的do_callback看起来像:
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 (args.size() < num_args) {
throw;
}