Lambda 函数作为基类

Posted

技术标签:

【中文标题】Lambda 函数作为基类【英文标题】:Lambda functions as base classes 【发布时间】:2013-08-28 05:48:54 【问题描述】:

在玩 Lambda 时,我发现了一个我不完全理解的有趣行为。

假设我有一个源自 2 个模板参数的 struct Overload,并且有一个 using F1::operator(); 子句。

现在,如果我从两个函子派生,我只能访问 F1 的 operator()(如我所料)

如果我从两个 Lambda 函数派生,这将不再正确:我也可以从 F2 访问 operator()。

#include <iostream>

// I compiled with g++ (GCC) 4.7.2 20121109 (Red Hat 4.7.2-8)
//
// g++ -Wall -std=c++11 -g main.cc
// g++ -Wall -std=c++11 -DFUNCTOR -g main.cc
// 
// or clang clang version 3.3 (tags/RELEASE_33/rc2)
// 
// clang++ -Wall -std=c++11 -g main.cc
// clang++ -Wall -std=c++11 -DFUNCTOR -g main.cc
// 
// on a Linux localhost.localdomain 3.9.6-200.fc18.i686 #1 SMP Thu Jun 13 
// 19:29:40 UTC 2013 i686 i686 i386 GNU/Linux box


struct Functor1

    void operator()()  std::cout << "Functor1::operator()()\n"; 
;

struct Functor2

    void operator()(int)  std::cout << "Functor2::operator()(int)\n"; 
;

template <typename F1, typename F2>
struct Overload : public F1, public F2

    Overload()
        : F1()
        , F2() 

    Overload(F1 x1, F2 x2)
        : F1(x1)
        , F2(x2) 

    using F1::operator(); 
;

template <typename F1, typename F2>
auto get(F1 x1, F2 x2) -> Overload<F1, F2>

   return Overload<F1, F2>(x1, x2);



int main(int argc, char *argv[])

    auto f = get(Functor1(), Functor2());

    f();
#ifdef FUNCTOR
    f(2); // this one doesn't work IMHO correctly
#endif

    auto f1 = get(
                  []()  std::cout << "lambda1::operator()()\n"; ,
                  [](int)  std::cout << "lambda2::operator()(int)\n"; 
                  );
    f1();
    f1(2); // this one works but I don't know why


  return 0;

标准规定:

lambda 表达式的类型(也是闭包对象的类型) 是唯一的、未命名的非联合类类型

所以每个 Lambda 的类型都应该是唯一的。

我无法解释为什么会这样:有人能解释一下吗?

【问题讨论】:

欢迎来到 Stack Overflow,非常好的问题! 非常喜欢。我们是一群顽强的人,你刚刚教会了我们很多新东西。 【参考方案1】:

除了operator(),由 lambda 定义的类可以(在适当的情况下)提供到函数指针的转换。情况(或至少是主要情况)是 lambda 无法捕获任何东西。

如果您添加捕获:

auto f1 = get(
              []()  std::cout << "lambda1::operator()()\n"; ,
              [i](int)  std::cout << "lambda2::operator()(int)\n"; 
              );
f1();
f1(2);

...不再提供到pointer to function 的转换,因此尝试编译上面的代码会出现您可能一直预期的错误:

trash9.cpp: In function 'int main(int, char**)':
trash9.cpp:49:9: error: no match for call to '(Overload<main(int, char**)::<lambda()>, main(int, char**)::<lambda(int)> >) (int)'
trash9.cpp:14:8: note: candidate is:
trash9.cpp:45:23: note: main(int, char**)::<lambda()>
trash9.cpp:45:23: note:   candidate expects 0 arguments, 1 provided

【讨论】:

等等。你是说你可以从函数指针类型继承? @zneak:不,一点也不。您可以从 lambda 继承,因为它定义了一个类,而不是一个函数。 struct Overload : public F1, public F2,如果我没看错的话,根据你的假设,F1 和 F2 被推断为函数指针类型。 @zneak: 否 - 在这种情况下,lambda 具有 operator (function-pointer-type)() 成员。这些成员在派生类中被继承并且“可见”。所以f1(2)是“两步”转换为函数指针+函数调用。 (如果我没猜错的话。) @jrok:这个答案是完全正确的。 OP 示例中对f1 的两次调用正在做不同的事情。一种是调用 lambda 的 operator()(由 using 子句引入)。另一种是调用函数指针(隐式转换的结果)。 Jerry 指出 lambda 不仅仅提供 operator()(如 OP 的 Functor1Functor2);当它们是无状态时,它们还提供到函数指针的隐式转换。【参考方案2】:

一个 lambda 生成一个仿函数类。

确实,您可以从 lambdas 派生并拥有多态 lambdas!

#include <string>
#include <iostream>

int main()

    auto overload = make_overload(
        [](int i)           return '[' + std::to_string(i) + ']'; ,
        [](std::string s)   return '[' + s + ']'; ,
        []                  return "[void]"; 
        );

    std::cout << overload(42)              << "\n";
    std::cout << overload("yay for c++11") << "\n";
    std::cout << overload()                << "\n";

打印

[42]
[yay for c++11]
[void]

怎么做?

template <typename... Fs>
   Overload<Fs...> make_overload(Fs&&... fs)

    return  std::forward<Fs>(fs)... ;

当然……这仍然隐藏了魔法。正是 Overload 类“神奇地”派生自所有 lambda,并公开了相应的 operator()

#include <functional>

template <typename... Fs> struct Overload;

template <typename F> struct Overload<F> 
    Overload(F&& f) : _f(std::forward<F>(f))  

    template <typename... Args>
    auto operator()(Args&&... args) const 
    -> decltype(std::declval<F>()(std::forward<Args>(args)...)) 
        return _f(std::forward<Args>(args)...);
    

  private:
    F _f;
;

template <typename F, typename... Fs>
   struct Overload<F, Fs...> : Overload<F>, Overload<Fs...>

    using Overload<F>::operator();
    using Overload<Fs...>::operator();

    Overload(F&& f, Fs&&... fs) :  
        Overload<F>(std::forward<F>(f)),
        Overload<Fs...>(std::forward<Fs>(fs)...)
    
    
;

template <typename... Fs>
   Overload<Fs...> make_overload(Fs&&... fs)

    return  std::forward<Fs>(fs)... ;

Live on Coliru

【讨论】:

虽然这很有趣,但我看不出它是如何回答这个问题的。 @Nemo 啊。显然我稍微误读了这个问题。尽管如此,我的答案中的代码似乎与 OP 的说法相矛盾,即“如果我从两个 Lambda 函数派生,这不再正确:我也可以从 F2 访问 operator()”。如果您丢失了using base::operator() 行,则代码将不再起作用。我确实认为这是因为我选择了链式继承,而不是多重继承

以上是关于Lambda 函数作为基类的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin函数 ⑥ ( 函数参数为 Lambda 表达式 | Lambda 表达式作为参数的简略写法 | 唯一参数的简略写法 | 最后一个参数的简略写法 )

Lambda 作为函数参数

我可以在类中有一个命名的 lambda 函数作为变量并尝试从主函数调用命名的 lambda 吗?

C ++ lambda函数作为类成员:奇怪的行为

如何传递和使用任意lambda函数作为参数[重复]

将 lambda 作为模板参数传递给函数指针函数模板化