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 的 Functor1
和 Functor2
);当它们是无状态时,它们还提供到函数指针的隐式转换。【参考方案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 表达式作为参数的简略写法 | 唯一参数的简略写法 | 最后一个参数的简略写法 )