Lambda作为基类


69

在Lambdas上玩耍时,我发现了一个有趣的行为,但我并没有完全理解。

假设我有一个struct Overload从2个模板参数派生的,并且有一个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的类型都应该是唯一的。

我无法解释为什么会这样:请问有人对此有所了解吗?


9
欢迎来到Stack Overflow,这是一个很好的问题!

1
非常。我们是一个艰难的人群,您刚刚教会了我们很多新知识。
zneak 2013年

Answers:


35

除之外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 2013年

@zneak:不,一点也不。您可以从lambda继承,因为它定义了一个类,而不是一个函数。
杰里·科芬

struct Overload : public F1, public F2,如果您没看错的话,可以根据您的假设推导F1和F2作为函数指针类型。
zneak

6
@zneak:否-operator (function-pointer-type)()在这种情况下,lambda具有成员。这些成员在派生类中是继承的并且是“可见的”。所以f1(2)是“两步走”的转换函数指针+函数调用。(如果我说的没错。)
Mat

7
@jrok:这个答案是完全正确的。f1OP的示例中的两次调用正在执行不同的操作。一种是调用lambda operator()(由该using子句拉入)。另一种是调用函数指针(隐式转换的结果)。Jerry指出,lambda不仅提供其他功能operator()(例如OPFunctor1Functor2);当它们为无状态时,它们还提供对函数指针的隐式转换。
Nemo

14

Lambda生成函子类。

实际上,您可以从lambda派生并拥有多态lambda!

#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)... };
}

看到它住在Coliru


8
尽管这很有趣,但我看不出它如何回答这个问题。
Nemo

@尼莫啊。显然我稍微误解了这个问题。但是,我的答案中的代码似乎与OP的说法相矛盾:“如果我从两个Lambda函数派生,这将不再成立:我也可以从F2访问operator()”。如果您丢失该using base::operator()行,则该代码将无法再使用。我确实认为这是因为我选择了链式继承,而不是多重继承
13年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.