C++11 "overloaded lambda",带可变参数模板和变量捕获

C++11 "overloaded lambda" with variadic template and variable capture

本文关键字:参数 变量 变参 lambda overloaded C++11      更新时间:2023-10-16

我正在研究c++ 11的一个习惯用法,可以称为"重载lambda":

  • http://cpptruths.blogspot.com/2014/05/fun -与-λ- c14 -风格的一部分- 2. - html
  • http://martinecker.com/martincodes/lambda-expression-overloading/

用可变模板重载n函数对我来说似乎很有吸引力,但事实证明它不适用于变量捕获:任何[&] [=] [y] [&y](和[this]等如果在成员函数中)导致编译失败:error: no match for call to '(overload<main(int, char**)::<lambda(int)>, main(int, char**)::<lambda(char*)> >) (char*&)'(与我的本地GCC 4.9.1和ideone.com GCC 5.1)

另一方面,固定的2元情况没有这个问题。(尝试在ideone.com上更改第一个#if 0#if 1)

你知道这里发生了什么吗?这是一个编译器错误,还是我偏离了c++ 11/14规范?

http://ideone.com/dnPqBF

#include <iostream>
using namespace std;
#if 0
template <class F1, class F2>
struct overload : F1, F2 {
  overload(F1 f1, F2 f2) : F1(f1), F2(f2) { }
  using F1::operator();
  using F2::operator();
};
template <class F1, class F2>
auto make_overload(F1 f1, F2 f2) {
  return overload<F1, F2>(f1, f2);
}
#else
template <class... Fs>
struct overload;
template <class F0, class... Frest>
struct overload<F0, Frest...> : F0, overload<Frest...> {
  overload(F0 f0, Frest... rest) : F0(f0), overload<Frest...>(rest...) {}
  using F0::operator();
};
template <>
struct overload<> {
  overload() {}
};
template <class... Fs>
auto make_overload(Fs... fs) {
  return overload<Fs...>(fs...);
}
#endif
#if 0
#define CAP
#define PRINTY()
#else
#define CAP y
#define PRINTY() cout << "int y==" << y << endl
#endif
int main(int argc, char *argv[]) {
    int y = 123;
    auto f = make_overload(
        [CAP] (int x) { cout << "int x==" << x << endl; PRINTY(); },
        [CAP] (char *cp) { cout << "char *cp==" << cp << endl; PRINTY(); });
    f(argc);
    f(argv[0]);
}

重载解析只对存在于公共作用域中的函数有效。这意味着第二个实现无法找到第二个重载,因为您没有从overload<Frest...>导入函数调用操作符到overload<F0, Frest...>

然而,非捕获lambda类型定义了一个函数指针的转换操作符,其签名与lambda的函数调用操作符相同。该转换操作符可以通过名称查找找到,这是在删除捕获部分时调用的操作符。

正确的实现,适用于捕获和非捕获lambda,并且总是调用operator()而不是转换操作符,应该如下所示:

template <class... Fs>
struct overload;
template <class F0, class... Frest>
struct overload<F0, Frest...> : F0, overload<Frest...>
{
    overload(F0 f0, Frest... rest) : F0(f0), overload<Frest...>(rest...) {}
    using F0::operator();
    using overload<Frest...>::operator();
};
template <class F0>
struct overload<F0> : F0
{
    overload(F0 f0) : F0(f0) {}
    using F0::operator();
};
template <class... Fs>
auto make_overload(Fs... fs)
{
    return overload<Fs...>(fs...);
}

在c++17中,有了类模板参数推导和using声明的包扩展,上述实现可以简化为:

template <typename... Ts> 
struct overload : Ts... { using Ts::operator()...; };
template <typename... Ts>
overload(Ts...) -> overload<Ts...>;

演示2

c++ 11中扁平版本的重载

回复对已接受答案的评论,这里有一个完全不使用递归模板的版本。这允许尽可能多的重载,只要你需要,只调用一个侧模板。

  namespace details {
    template<class F>
    struct ext_fncall : private F {
      ext_fncall(F v) :
        F(v) {}
      
      using F::operator();
    };
  }
  
  template<class... Fs>
  struct overload : public details::ext_fncall<Fs>... {
    overload(Fs... vs) :
      details::ext_fncall<Fs>(vs)... {}
  };
  
  template<class... Fs>
  overload<Fs...> make_overload(Fs... vs) {
    return overload<Fs...> {vs...};
  }
<标题>

侧模板ext_fncall<class F>派生自一个给定的函子,并且只公开它的operator(),它模仿了给定的c++ 11版本。

实际的overload<class... Fs>派生自ext_fncall<Fs>...,这意味着它只从它派生的类中公开operator()(由于ext_fncall<F>,其他成员不能被访问)。