首页 > 解决方案 > 模板函数包裹的lamda类型推导

问题描述

我实现了std::bind如下版本的自定义替代方案:

  template <typename F, typename ... Ts>                                           
  constexpr auto curry(F &&f, Ts ... args) {                                       
    return [&](auto&& ... args2) {                                                 
      return f(std::forward<Ts...>(args...), args2...);                            
    };                                                                             
  }  

此函数获取要应用的参数的函数和包,并返回f带有部分应用参数的 lambda args

另外,为了测试这段代码,我有一个函数,它获取 2 个参数并只返回第一个:

  template <typename T1, typename T2>                                          
  constexpr T1 fconst(T1 &&x, T2&&) {                                          
    return x;                                                                  
  }   

当我使用curry如下功能时:

int main() {                                                                       
                                                                                   
  auto z2 = curry(fconst, 5);                                                                                                                                        
                                                                                   
  return 0;                                                                        
}       

我收到编译器无法推断F类型的错误:

error: no matching function for call to ‘curry(<unresolved overloaded function type>, int)’
   17 |   auto z2 = curry(fconst, 5);
candidate: ‘template<class F, class ... Ts> constexpr auto curry(F&&, Ts ...)’
   15 |   constexpr auto curry(F &&f, Ts ... args) {
      |                  ^~~~~
note:   template argument deduction/substitution failed:
note:   couldn’t deduce template parameter ‘F’
   17 |   auto z2 = curry(fconst, 5);


但是当我curry将函数实现为宏而不是模板函数时:

#define curry(f, args...) \                                                        
  [&](auto&& ... args2) { \                                                        
    return f(args, args2...); \                                                    
  };  

我的主要代码编译成功。

我使用启用了 g++11 和 c++20。

我的问题是:

  1. 为什么编译器不能推断F模板的类型,但可以在传递 lambda 时做到这一点?
  2. 我的curry函数可以使用模板函数来实现,还是宏是唯一可行的方法?

标签: c++templateslambdag++c++20

解决方案


首先,你curry是危险的,因为里面的 lambda 通过引用捕获本地参数。一旦curry返回,您就会有悬空的裁判。

正确的版本是:

  template <typename F, typename ... Ts>                                           
  constexpr auto curry(F &&f, Ts ... args) {                                       
    return [...args=std::move(args), f=std::forward<F>(f)](auto&& ... args2) mutable {                                                 
      return std::invoke(f,args..., std::forward<decltype(args2)>(args2)...);                            
    };                                                                             
  }  
  • f,args处理 显示了两种相同的样式。
    • 在调用方复制 - args
    • 或在捕获时复制/移动(向前)。
  • (参数包捕获是 C++20)。使用元组和 有一个更丑陋的解决方法std::apply,询问您是否需要它。
  • std::bind从不移动其捕获的参数,它们总是作为左值传递给被调用者。这使得重复调用安全。
  • args2应该正确转发。

不幸的是,没有无宏的解决方案。这是重载函数集的固有问题。不能传递这样的集合,C++ 根本不支持。有一些论文试图解决这个问题 - 我知道P1170R0。但到目前为止没有一个被接受。

解决方法本质上是您想出的以及为什么您的第二个示例有效 - 宏能够粘贴函数名称,无论它是否重载(或者它实际上是什么)。

#define overload_set(overloaded_f) \                                                        
  [](auto&& ... args) { \                                                        
    return overloaded_f(std::forward<decltype(args)>(args)...);} \                                                    

这是就地完美转发 lambda,overloaded_f内部带有复制粘贴符号。尽管如此,一个人不能像任何其他普通函数一样获取 this 的地址(或者更确切地说不应该),但它可以传递给 eg curry

完整示例

#include <iostream>

template <typename F, typename... Ts>
constexpr auto curry(F&& f, Ts... args) {
    return [... args = std::move(args),
            f = std::forward<F>(f)](auto&&... args2) mutable {
        return f(args..., std::forward<decltype(args2)>(args2)...);
    };
}
template <typename T1, typename T2>
constexpr T1 fconst(T1&& x, T2&&y) {
    std::cout<<"Value x:" << x<<'\n';
    std::cout<<"Value y:" << y<<'\n';
    return x;
}

#define overload_set(overloaded_f)                              \
  [&](auto&&... args) {                                         \
    return overloaded_f(std::forward<decltype(args)>(args)...); \
    }

struct Foo{
    Foo()=default;
    Foo(Foo&&)=default;
    Foo(const Foo&)=delete;

    operator int(){ return 42;}
};

int main() { 
    // Overloaded function can now be stored in a lambda.
    auto stored_set= overload_set(fconst);
    // And called as ordinary function.
    auto ret =stored_set(1.0, 2.0); 
    std::cout<<"Returned value: " <<ret <<'\n';
    // Overloaded set now can be passed around.
    // But it is a functor, not a function.
    auto curried_fnc= curry(overload_set(fconst),1.0);
    // Curry works
    auto ret2 = curried_fnc(2.0);
    std::cout<<"Returned value: " <<ret2<<'\n';

    // Move-only values work too.
    std::cout<<"Curry move\n";
    curried_fnc(Foo{}); 
}

输出

Value x:1
Value y:2
Returned value: 1
Value x:1
Value y:2
Returned value: 1
Curry move 
Value x:1
Value y:42

推荐阅读