包装在模板函数中的 lamda 的类型推导

Type deduction for lamda wrapped in template function

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

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

此函数获取要应用的参数的函数和包,以及 returns lambda,它是 f 具有部分应用的参数 args

另外,为了测试这段代码,我有一个函数,它有两个参数,只有 returns 第一个:

  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 函数可以使用模板函数实现吗,或者宏是唯一可行的方法?

首先,您的 curry 很危险,因为里面的 lambda 通过引用捕获本地参数。一旦 curry return 你有悬空的引用。

正确的版本是:

  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)...);                            
    };                                                                             
  }  
  • fargs处理显示了两种相同的样式。
    • 在调用方复制 - args.
    • 或copy/move(在捕获时)。
  • (参数包捕获是C++20)。有一个使用元组和 std::apply 的更丑陋的解决方法,询问您是否需要它。
  • std::bind 从不移动它捕获的参数,它们总是作为左值传递给被调用者。这使得重复调用安全。
  • args2应该正确转发。

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

解决方法基本上就是您提出的方法以及第二个示例起作用的原因 - 宏能够粘贴函数名称,无论它是否重载(或者它实际上是什么)。

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

这是就地完美转发 lambda,其中包含复制粘贴的 overloaded_f 符号。尽管如此,人们仍然不能像任何其他普通函数一样获取 this(或者更确切地说不应该)的地址,但它可以传递给例如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