用变体类型列表调用std :: function

时间:2019-05-08 01:29:42

标签: c++ c++11 c++14

我正在使用第三方库,该库使用自定义向量和变量类型传递值。但是,这些向量将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(或同等学历)

2 个答案:

答案 0 :(得分:3)

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

此帮助程序无需拆分功能。它使我们可以随意访问未打包的索引包。

对索引器的调用将返回一个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。

答案 1 :(得分:2)

您需要某种方法将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... 
  }

对于第二部分,您需要在编译时从所需的向量中选择然后元素数量。这就是为什么这样做很危险的原因,向量必须必须具有相等或更多的元素,然后再指定否则会导致不确定的行为。

可以在问题here中找到解压缩向量的解决方案(但是请使用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;
   }