【问题标题】:Passing lambdas as template parameters: what type is actually deduced?将 lambdas 作为模板参数传递:实际推导出的类型是什么?
【发布时间】:2019-04-04 17:26:19
【问题描述】:

如果我将 lambda 作为模板参数传递,那么推导出的参数的实际类型是什么?我查看了 VS2017 调试器和这个 lambda 的类型:[](int x) {return x; }filename::__I2::int<lambda>(int)

我问这个的原因是因为我想传递一个 lambda,然后从中创建一个内部 std::function。请注意,这与this answer 以及为什么我们必须使用 CTAD 构造内部std::function 而不仅仅是将模板参数传递给std::function 相关。

例如,我想做如下的事情:

template<class Func, class... Args> 
void createStdFunc(Func f, Args... args) {
    std::function<Func> internalFunc = f; //this does not work
}

//usage
createStdFunc([](int x) {return x; }, 5);

但是,这不起作用,我收到错误'initialising' cannot convert from 'Func' to 'std::function&lt;Func&gt;'。我不确定这些类型有何不同以及它们从传递到函数到初始化std::function 的变化。请注意,我知道您可以从 2017 年开始使用 CTAD,但想知道 2014 年及之前的解决方案是什么?

【问题讨论】:

  • 每个 lambda 都有一个唯一的类型,编译器知道,但你不能直接命名自己。但是,它们都可以转换为(正确声明)std::functions。
  • @JesperJuhl,是的-但我想知道为什么,在上面的示例中,如果 f 是一种 Func 那么为什么我不能将 f 分配给 std::function ?我可能理解错了类型。
  • 另外,lambda 的类型不是函数类型,它是std::function 的模板参数所必需的。例如您需要createStdFunc&lt;int(int)&gt;([](int x) {return x; }, 5); 才能使用int(int) 作为Func 模板参数,而不是lambda 类型。
  • @DanielSchepler - 是的 - 所以我必须有某种方法从 lambda 中提取类型才能做到这一点?我想知道 CTAD 是如何做到这一点的。
  • 您究竟想用std::function 做什么?您同时获得所有参数 - 您是否只想懒惰地绑定它们(例如 internalFunc 不应该接受任何参数?)还是什么?看到std::function 和所有论点对我来说有点奇怪 - 所以我想知道您要解决的实际问题是什么。

标签: c++ lambda c++14 std-function template-argument-deduction


【解决方案1】:

在 C++14 中,您可以使用返回类型推导来计算函数签名,这意味着传递给 createStdFunc 的参数类型匹配:

template<class Func, class... Args> 
void createStdFunc(Func f, Args... args) {
    std::function<std::result_of_t<Func(Args...)> (Args...)> internalFunc{f}; //this does work
}

【讨论】:

    【解决方案2】:

    我的方式

    #include <iostream>
    #include <functional>
    
    template <typename R, typename T, typename ... As>
    constexpr std::function<R(As...)> getFuncType (R(T::*)(As...) const);
    
    template <typename F, typename ... As>
    void createStdFunc (F const & f, As ... as)
     {
       decltype(getFuncType(&F::operator()))  internalFunc { f };
    
       internalFunc(as...);
     }
    
    int main ()
     {
       createStdFunc([](int x) { std::cout << x << std::endl; }, 5);
     }
    

    也可以通过using

    template <typename F>
    using funcType = decltype(getFuncType(&F::operator()));
    
    template <typename F, typename ... As>
    void createStdFunc (F const & f, As ... as)
     {
       funcType<F> internalFunc { f };
    
       internalFunc(as...);
     }
    

    【讨论】:

      【解决方案3】:

      代码中的问题是Func 不是函数类型。它是 lambda 的类型。 Lambda 编译成这样:

      // equivalent:
      // auto my_lambda = [](int v){ return v; };
      
      struct /* unnamed */ {
          auto operator()(int v) const { return v; }
      } my_lambda;
      

      解决方案是从闭包类型中提取operator() 的类型:

      using my_lambda_t = decltype(my_lambda);
      
      // type: int(my_lambda_t::*)(int) const; 
      auto call_operator = &decltype(my_lambda_t)::operator();
      

      然后,根据operator()的类型,可以推断出参数的类型和返回类型:

      template<typename>
      struct extract_types {};
      
      template<typename R, typename C, typename... Args>
      struct extract_types<R(C::*)(Args...) const> {
          using result = R;
          using args_types = std::tuple<Args...>;
      };
      

      此模式的通用版本在 Boost.CallableTraits 中提供

      【讨论】:

        【解决方案4】:

        您可以编写一个简单的特征来概括可调用类型。如果您使用operator()const 和非const)同时处理函数指针和任何内容,您应该能够涵盖大多数用例。

        #include <tuple>
        
        // For callable types
        template<class T>
        struct func_type : func_type<decltype(&T::operator())>{};
        
        // For callable types' member functions (including `operator()`)
        template<class T, class R, class ... Args >
        struct func_type<R (T::*)(Args...) const> : func_type<R(*)(Args...)> {};
        
        // For function pointers
        template<class R, class ... Args >
        struct func_type<R (*)(Args...)> {
            using type = R(Args...);
            using result = R;
            using args = std::tuple<Args...>;
        };
        
        template<class T>
        using func_type_t = typename func_type<T>::type;
        

        func_type_t&lt;T&gt; 应该为您提供大多数可调用类型T 的函数类型。使用示例:

        #include <functional>
        
        template<class Func, class... Args>
        void createStdFunc(Func f, Args... args) {
            // Replaced `Func` with `func_type_t<Func>`
            std::function<func_type_t<Func>> internalFunc = f;
        }
        
        int foo(int x) { return x; }
        
        struct bar {
            int operator()(int x) { return x; };
        };
        
        
        int main()
        {
            // With lambda expression
            createStdFunc([](int x) {return x; }, 5);
        
            // With function pointer
            createStdFunc(foo, 5);
        
            // With std::function
            std::function<int(int)> std_func = [](int x) {return x; };
            createStdFunc(std_func, 5);
        
            // With a functor
            createStdFunc(bar{}, 5);
        }
        

        【讨论】:

          【解决方案5】:

          std::function 模板需要一个函数类型作为其参数,从该函数类型推断要包装的可调用对象的返回值和参数类型。 lambda 表达式的闭包类型是可调用的,但它不是函数类型。

          C++17 为std::function 引入了deduction guides,它允许从任何可调用的参数中推断出正确的类型。在 C++17 之前,您可以使用一组帮助模板来推断正确的类型,例如:

          template <typename F>
          struct deduce_func_type_helper;
          
          template <typename R, typename... Args>
          struct deduce_func_type_helper<R(&)(Args...)>
          {
              using type = std::function<R(Args...)>;
          };
          
          template <typename R, typename... Args>
          struct deduce_func_type_helper<R(*)(Args...)> : deduce_func_type_helper<R(&)(Args...)> {};
          
          template <typename C, typename R, typename... Args>
          struct deduce_func_type_helper<R(C::*)(Args...)> : deduce_func_type_helper<R(&)(Args...)> {};
          
          template <typename C, typename R, typename... Args>
          struct deduce_func_type_helper<R(C::*)(Args...) const> : deduce_func_type_helper<R(&)(Args...)> {};
          
          template <typename C, typename R, typename... Args>
          struct deduce_func_type_helper<R(C::*)(Args...) volatile> : deduce_func_type_helper<R(&)(Args...)> {};
          
          template <typename F>
          struct deduce_func_type_helper<F&> : deduce_func_type_helper<std::remove_cv_t<F>> {};
          
          template <typename F>
          struct deduce_func_type_helper<F&&> : deduce_func_type_helper<std::remove_cv_t<F>> {};
          
          template <typename F>
          struct deduce_func_type_helper : deduce_func_type_helper<decltype(&F::operator())> {};
          
          template <typename F>
          using func_type_t = typename deduce_func_type_helper<F>::type;
          

          live example here

          请注意,上面的例子并不完整;它缺少一些专业化,例如,constvolatile 和不同 ref 限定符的所有可能组合。所以这可能会变得非常冗长,如果可以的话,你可能会想要使用 C++17……

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-04-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-11-29
            相关资源
            最近更新 更多