首页 文章

“解包”一个元组来调用匹配的函数指针

提问于
浏览
236

我试图在_363229中存储不同数量的值,这些值稍后将用作调用与存储类型匹配的函数指针的参数 .

我创建了一个简化的示例,显示了我正在努力解决的问题:

#include <iostream>
#include <tuple>

void f(int a, double b, void* c) {
  std::cout << a << ":" << b << ":" << c << std::endl;
}

template <typename ...Args>
struct save_it_for_later {
  std::tuple<Args...> params;
  void (*func)(Args...);

  void delayed_dispatch() {
     // How can I "unpack" params to call func?
     func(std::get<0>(params), std::get<1>(params), std::get<2>(params));
     // But I *really* don't want to write 20 versions of dispatch so I'd rather 
     // write something like:
     func(params...); // Not legal
  }
};

int main() {
  int a=666;
  double b = -1.234;
  void *c = NULL;

  save_it_for_later<int,double,void*> saved = {
                                 std::tuple<int,double,void*>(a,b,c), f};
  saved.delayed_dispatch();
}

通常对于涉及 std::tuple 或可变参数模板的问题,我会编写另一个模板,如 template <typename Head, typename ...Tail> ,逐个递归地评估所有类型,但我看不到这样做的方式来调度函数调用 .

这个的真正动机有点复杂,它通过 Contract 从另一个接口传递元组,所以不能改变,但是将它解压缩到函数调用的愿望是我的 . 这排除了使用 std::bind 作为避免潜在问题的廉价方法 .

什么是使用 std::tuple 调度调用的干净方法,或者是另一种更好的方法来实现存储/转发某些值和函数指针直到任意未来点的相同结果?

8 回答

  • 40

    这有点复杂(即使有可能) . 我建议你使用已经实现的库,即Boost.Fusioninvoke函数) . 作为奖励,Boost Fusion也与C 03编译器一起使用 .

  • 2

    我使用C 14 std :: index_sequence(函数返回类型作为模板参数RetT)从Johannes获得的解决方案的变体:

    template <typename RetT, typename ...Args>
    struct save_it_for_later
    {
        RetT (*func)(Args...);
        std::tuple<Args...> params;
    
        save_it_for_later(RetT (*f)(Args...), std::tuple<Args...> par) : func { f }, params { par } {}
    
        RetT delayed_dispatch()
        {
            return callFunc(std::index_sequence_for<Args...>{});
        }
    
        template<std::size_t... Is>
        RetT callFunc(std::index_sequence<Is...>)
        {
            return func(std::get<Is>(params) ...);
        }
    };
    
    double foo(int x, float y, double z)
    {
      return x + y + z;
    }
    
    int testTuple(void)
    {
      std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
      save_it_for_later<double, int, float, double> saved (&foo, t);
      cout << saved.delayed_dispatch() << endl;
      return 0;
    }
    
  • 3

    这是一个完整的可编辑版本的Johanne's solution到awoodland的问题,希望它可能对某人有用 . 这是用Debian挤压的g 4.7快照测试的 .

    ###################
    johannes.cc
    ###################
    #include <tuple>
    #include <iostream>
    using std::cout;
    using std::endl;
    
    template<int ...> struct seq {};
    
    template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
    
    template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
    
    double foo(int x, float y, double z)
    {
      return x + y + z;
    }
    
    template <typename ...Args>
    struct save_it_for_later
    {
      std::tuple<Args...> params;
      double (*func)(Args...);
    
      double delayed_dispatch()
      {
        return callFunc(typename gens<sizeof...(Args)>::type());
      }
    
      template<int ...S>
      double callFunc(seq<S...>)
      {
        return func(std::get<S>(params) ...);
      }
    };
    
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wunused-parameter"
    #pragma GCC diagnostic ignored "-Wunused-variable"
    #pragma GCC diagnostic ignored "-Wunused-but-set-variable"
    int main(void)
    {
      gens<10> g;
      gens<10>::type s;
      std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
      save_it_for_later<int,float, double> saved = {t, foo};
      cout << saved.delayed_dispatch() << endl;
    }
    #pragma GCC diagnostic pop
    

    可以使用以下SConstruct文件

    #####################
    SConstruct
    #####################
    #!/usr/bin/python
    
    env = Environment(CXX="g++-4.7", CXXFLAGS="-Wall -Werror -g -O3 -std=c++11")
    env.Program(target="johannes", source=["johannes.cc"])
    

    在我的机器上,这给了

    g++-4.7 -o johannes.o -c -Wall -Werror -g -O3 -std=c++11 johannes.cc
    g++-4.7 -o johannes johannes.o
    
  • 6

    您需要构建一个数字参数包并将其解压缩

    template<int ...>
    struct seq { };
    
    template<int N, int ...S>
    struct gens : gens<N-1, N-1, S...> { };
    
    template<int ...S>
    struct gens<0, S...> {
      typedef seq<S...> type;
    };
    
    
    // ...
      void delayed_dispatch() {
         callFunc(typename gens<sizeof...(Args)>::type());
      }
    
      template<int ...S>
      void callFunc(seq<S...>) {
         func(std::get<S>(params) ...);
      }
    // ...
    
  • 31

    这是一个C 14解决方案 .

    template <typename ...Args>
    struct save_it_for_later
    {
      std::tuple<Args...> params;
      void (*func)(Args...);
    
      template<std::size_t ...I>
      void call_func(std::index_sequence<I...>)
      { func(std::get<I>(params)...); }
      void delayed_dispatch()
      { call_func(std::index_sequence_for<Args...>{}); }
    };
    

    这仍然需要一个辅助函数( call_func ) . 由于这是一个常见的习惯用法,或许标准应该直接支持它作为 std::call 可能的实现

    // helper class
    template<typename R, template<typename...> class Params, typename... Args, std::size_t... I>
    R call_helper(std::function<R(Args...)> const&func, Params<Args...> const&params, std::index_sequence<I...>)
    { return func(std::get<I>(params)...); }
    
    // "return func(params...)"
    template<typename R, template<typename...> class Params, typename... Args>
    R call(std::function<R(Args...)> const&func, Params<Args...> const&params)
    { return call_helper(func,params,std::index_sequence_for<Args...>{}); }
    

    然后我们的延迟派遣变成了

    template <typename ...Args>
    struct save_it_for_later
    {
      std::tuple<Args...> params;
      std::function<void(Args...)> func;
      void delayed_dispatch()
      { std::call(func,params); }
    };
    
  • 44

    C 17解决方案只是使用 std::apply

    auto f = [](int a, double b, std::string c) { std::cout<<a<<" "<<b<<" "<<c<< std::endl; };
    auto params = std::make_tuple(1,2.0,"Hello");
    std::apply(f, params);
    

    只是觉得应该在这个帖子的答案中说明一次(之后它已经出现在其中一条评论中) .


    此线程中仍然缺少基本的C 14解决方案 . 编辑:不,它实际上在沃尔特的答案中 .

    给出了这个功能:

    void f(int a, double b, void* c)
    {
          std::cout << a << ":" << b << ":" << c << std::endl;
    }
    

    使用以下代码段调用它:

    template<typename Function, typename Tuple, size_t ... I>
    auto call(Function f, Tuple t, std::index_sequence<I ...>)
    {
         return f(std::get<I>(t) ...);
    }
    
    template<typename Function, typename Tuple>
    auto call(Function f, Tuple t)
    {
        static constexpr auto size = std::tuple_size<Tuple>::value;
        return call(f, t, std::make_index_sequence<size>{});
    }
    

    例:

    int main()
    {
        std::tuple<int, double, int*> t;
        //or std::array<int, 3> t;
        //or std::pair<int, double> t;
        call(f, t);    
    }
    

    DEMO

  • 261

    c++14解决方案 . 首先,一些实用工具:

    template<std::size_t...Is>
    auto index_over(std::index_sequence<Is...>){
      return [](auto&&f)->decltype(auto){
        return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
      };
    }
    template<std::size_t N>
    auto index_upto(std::integral_constant<std::size_t, N> ={}){
      return index_over( std::make_index_sequence<N>{} );
    }
    

    这些允许您使用一系列编译时整数调用lambda .

    void delayed_dispatch() {
      auto indexer = index_upto<sizeof...(Args)>();
      indexer([&](auto...Is){
        func(std::get<Is>(params)...);
      });
    }
    

    我们完成了 .

    index_uptoindex_over 允许您使用参数包而无需生成新的外部重载 .

    当然,在c++17你就是

    void delayed_dispatch() {
      std::apply( func, params );
    }
    

    现在,如果我们喜欢,在c++14我们可以写:

    namespace notstd {
      template<class T>
      constexpr auto tuple_size_v = std::tuple_size<T>::value;
      template<class F, class Tuple>
      decltype(auto) apply( F&& f, Tuple&& tup ) {
        auto indexer = index_upto<
          tuple_size_v<std::remove_reference_t<Tuple>>
        >();
        return indexer(
          [&](auto...Is)->decltype(auto) {
            return std::forward<F>(f)(
              std::get<Is>(std::forward<Tuple>(tup))...
            );
          }
        );
      }
    }
    

    相对容易,并准备好发送清洁c++17语法 .

    void delayed_dispatch() {
      notstd::apply( func, params );
    }
    

    当编译器升级时,只需用 std 替换 notstd ,bob就是你的叔叔 .

  • 19

    根据给出的答案更多地思考问题,我找到了解决同一问题的另一种方法:

    template <int N, int M, typename D>
    struct call_or_recurse;
    
    template <typename ...Types>
    struct dispatcher {
      template <typename F, typename ...Args>
      static void impl(F f, const std::tuple<Types...>& params, Args... args) {
         call_or_recurse<sizeof...(Args), sizeof...(Types), dispatcher<Types...> >::call(f, params, args...);
      }
    };
    
    template <int N, int M, typename D>
    struct call_or_recurse {
      // recurse again
      template <typename F, typename T, typename ...Args>
      static void call(F f, const T& t, Args... args) {
         D::template impl(f, t, std::get<M-(N+1)>(t), args...);
      }
    };
    
    template <int N, typename D>
    struct call_or_recurse<N,N,D> {
      // do the call
      template <typename F, typename T, typename ...Args>
      static void call(F f, const T&, Args... args) {
         f(args...);
      }
    };
    

    这需要将 delayed_dispatch() 的实现更改为:

    void delayed_dispatch() {
         dispatcher<Args...>::impl(func, params);
      }
    

    这可以通过递归将 std::tuple 转换为自己的参数包来实现 . 需要 call_or_recurse 作为终止使用真实调用的递归的专门化,它只是解压缩已完成的参数包 .

    我不确定这是否是一个“更好”的解决方案,但它是思考和解决它的另一种方式 .


    作为另一种替代解决方案,您可以使用 enable_if 来形成比我以前的解决方案更简单的东西:

    #include <iostream>
    #include <functional>
    #include <tuple>
    
    void f(int a, double b, void* c) {
      std::cout << a << ":" << b << ":" << c << std::endl;
    }
    
    template <typename ...Args>
    struct save_it_for_later {
      std::tuple<Args...> params;
      void (*func)(Args...);
    
      template <typename ...Actual>
      typename std::enable_if<sizeof...(Actual) != sizeof...(Args)>::type
      delayed_dispatch(Actual&& ...a) {
        delayed_dispatch(std::forward<Actual>(a)..., std::get<sizeof...(Actual)>(params));
      }
    
      void delayed_dispatch(Args ...args) {
        func(args...);
      }
    };
    
    int main() {
      int a=666;
      double b = -1.234;
      void *c = NULL;
    
      save_it_for_later<int,double,void*> saved = {
                                     std::tuple<int,double,void*>(a,b,c), f};
      saved.delayed_dispatch();
    }
    

    第一个重载只需要从元组中再获取一个参数并将其放入参数包中 . 第二个重载需要一个匹配的参数包,然后进行真正的调用,第一个重载在第二个只有第二个可行的情况下被禁用 .

相关问题