在 C++11 中实现干净的 lambda 函数

Posted

技术标签:

【中文标题】在 C++11 中实现干净的 lambda 函数【英文标题】:Achieving clean lambda functions in C++11 【发布时间】:2011-12-12 05:35:24 【问题描述】:

我一直在使用新的 C++11 lambda,完全指定模板参数的要求是一个真正的拖累。我希望喜欢使用的语法类似于以下内容:

#include <vector>
#include <algorithm>

struct foo

    void bar() 
;

int main()

    vector<foo> v(10);

    for_each(v.begin(), v.end(), [](f) f.bar(););
                                   ^^^

有什么办法可以得到接近这个的东西吗? Boost 的 Phoenix 库还可以,但是调用成员函数的语法需要 lots 样板——我想我是在 C++11 调用成员函数的简便性以及 Phoenix 的类型自动推导之后。

当前想法

我已经把它归结为这个语法:

vector<foo> x(1);
vector<bar> y(1);
for_each(x.begin(), x.end(), [](_a f) f->f(););
for_each(y.begin(), y.end(), [](_a b) b->b(););

这可行,但您必须为每种类型添加功能(例如ADD_AUTO_LAMBDA_SUPPORT(foo);)。它还有一个限制,即所有支持的类型都不能有任何模棱两可的成员。

完整的代码是:

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

struct foo

    foo() : x(3) 
    int x;
    void f()  cout << x << endl;
;

struct bar

    bar() : y(133.7) 
    double y;
    void b()  cout << y << endl;
;

struct combo : foo, bar  ;

struct _a

    _a(foo& f) : offset(reinterpret_cast<combo*>(&f)) 
    _a(bar& b) : offset(reinterpret_cast<combo*>((char*)&b - 2*sizeof(foo))) 

    combo* operator->()  return offset; 

private:
    combo* offset;
;

int main()

    vector<foo> x(1);
    vector<bar> y(1);

    for_each(x.begin(), x.end(), [](_a f) f->f(););
    for_each(y.begin(), y.end(), [](_a b) b->b(););

然后您可以使用一些模板和预处理器的魔法来生成_acombo,但是当您的名称不明确时就会出现问题(例如,带有b() 函数的第三个结构 - 您需要一种方法来消除我目前无法想到的歧义。

【问题讨论】:

我猜你可以使用const decltype(v)::value_type&amp; f,它对于很长的类型可能会更短。太糟糕了,我们使用auto 作为参数类型。无论如何,您添加的所有这些废话都是 IMO 的粗俗和不良做法。 你是说你做这一切是为了剃掉一个角色? for_each(x.begin(), x.end(), [](foo f) f-&gt;f();); 怎么了? (a) 因为它将算法与所使用的数据类型联系得更少,(b) 因为编译器 可以 计算出类型,因此应该,并且 (c ) 因为,当类型变得复杂时,它开始节省很多,并大大减少了行的混乱和样板:for_each(x.begin(), x.end(), [&amp;](const first_type&amp; a, const second_type&amp; b) return a + b; for_each(x.begin(), x.end(), [](a, b) return a + b; 节省了近 40 个字符! 或者当你开始在地图上操作时,for_each(m.begin(), m.end(), [&amp;](const map&lt;key_type, value_type&gt;::value_type&amp; x) ...for_each(m.begin(), m.end(), [&amp;](x) ... 一点背景信息:Herb Sutter 说 C++ 委员会考虑添加“auto”作为参数类型专门为此。但是,他们得出结论,正常功能也必须支持这一点。而且因为他们担心如此巨大的变化,尤其是在 C++11 开发的后期,会延迟整个标准,所以他们完全忽略了它。 【参考方案1】:

注意:我完全同意[](auto f) ... 会非常受欢迎!

虽然我们没有那个,但好老的typedef 呢?它只是添加了一行,非常“低技术”并且使 lambda 易于阅读:

typedef const map<key_type, value_type>::value_type&  λp_t;
for_each(m.begin(), m.end(), [&](λp_t x) ...);

【讨论】:

@Ayjay:嘿,为什么不呢? :-P(我不知道编译器是否会接受它。这只是为了好玩。) MIT Lisp 机器使用的旧“space cadet”键盘实际上有一个特殊的 lambda 键:en.wikipedia.org/wiki/Space-cadet_keyboard 该标准不允许在源代码中使用 unicode 字符。 @Ayjay 让我们为此编写一个重命名脚本作为预构建步骤! :) [jk] @phresnel - 当然,该标准允许实现支持 unicode 源字符。它不需要它,但它也不禁止它。【参考方案2】:

你可以使用decltype:

for_each(m.begin(), m.end(), [&](decltype(*m.begin()) x)...);

但你不能在匿名 lambda 中使用 auto 真的很糟糕。

更新:

你也可以

#define _A(CONTAINER_NAME) decltype(*CONTAINER_NAME.begin())
for_each(m.begin(), m.end(), [&](_A(m) x)  ... );

【讨论】:

我不会说他们是白痴,但我想知道每天有多少人使用 C++。 在 lambda 中使用 auto 的问题在于它会使 lambda 对象本身具有多态性,但在 C++ 中,任何对象(表达式)都必须具有明确定义的编译器时类型。如果我写:auto lambda = [](auto x) return x; ; lambda(1); lambda("foo");,会发生什么?也许它可以解析为template&lt;typename T&gt; operator()(T x) 或其他什么?但委员会现在对包含没有任何实际经验的复杂功能持谨慎态度(概念异常规范外部模板)。 rodrigo:我明白你的意思,但是,他们可以让匿名 lambdas 与 auto 一起使用,因为它会使语言更加方便。让我们面对现实吧,c++ 慢慢衰落的原因是因为它们似乎从未包含对大多数 c++ 程序员有用的功能。 @ViktorSehr 但是你必须定义匿名 lambda 是什么。然后,解析auto 类型的规则...请记住,当前auto 仅可用于自动(局部)变量,而不可用于函数参数。我认为lambda模板更可行:for_each(m.begin(), m.end(), template&lt;typename T&gt; [](T x) ... );. 这真是不公平。委员会非常希望在 C++11 中看到多态 lambda,它们被包含在最初的提案及其几个修订版中。不幸的是,由于太多未解决的问题,太少的时间,它们最终不得不从 C++11 中删除。委员会是保守的,是的,并且更喜欢没有功能 X 而不是损坏的功能 X,但这很难让他们成为白痴。【参考方案3】:

所以,大概是以下情况:

std::array<int,10> a;
for_each(begin(a),end(a),[](auto i) /* ... */ );

您希望编译器找出 lambda 接受一个 int 并将其读作:

for_each(begin(a),end(a),[](int i) /* ... */ );

问题在于 lambda 的类型会影响 for_each 模板实例化的类型,这可能会选择不同的特化,这反过来可能需要对 lambda 参数进行不同的类型推导。所以编译器根本没有合理的方法来使用算法代码来自动推断你传入的参数的类型。

无论如何,至少对于for_each算法你不需要这个,只需使用范围for循环:

for(auto i:a)  /* ... */ 

而在其他地方使用 decltype:

transform(begin(a),end(a),begin(a),[](decltype(*begin(a)) i)  return 2*i; );

【讨论】:

我想知道如果我们将模板化的 lambda 视为具有模板化函数调用运算符的非模板化函子是否会起作用。 我想像[]template&lt;typename T&gt;(T t) 这样的语法会很丑但可行。如果您的意思是使用 auto 关键字来执行此操作,它可能也必须适用于普通函数参数,并且它是一种新的模板语法,我认为这不是正确的方法。 这不需要任何特殊逻辑,只允许 lambdas 有选择地生成模板函子 auto lambda = [](auto x) return x; ==> struct lambda template &lt;class T&gt; T operator()(T x) return x; ; 我对让自动执行此操作的抱怨不是关于需要特殊逻辑。我的抱怨是,这基本上创建了一个新的模板语法,并且新的模板语法可能需要在 lambdas 之外的其他地方得到支持,例如普通函数的参数。 @bames53:为什么会这样?如果他们这样做,那就太好了,但我认为没有理由必须扩展常规功能。 Lambda 已经有了创建匿名结构的唯一必要步骤,为什么更改该结构的生成方式会影响常规函数定义?【参考方案4】:

您可以让 lambda 从函数中推断出它的参数(和类型),但是每个函数都必须使其可用。这讨论了如何在此处实现:

http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/c347334187628476?hl=en

基本上,您可以使用类似 ruby​​ 的方式调用函数。所以你会像这样调用for_each

$(for_each(some_range), x)

    printf("%i\n", x);
;

($) 美元符号是一个宏,负责从函数中推导类型。现在这不适用于std::for_each,它必须与一个专门定义的函数一起使用,该函数发出 lambda 的参数类型。

由于它使用宏,它不考虑运算符优先级,因此您不能将它与范围适配器一起使用。

【讨论】:

【参考方案5】:

如果您愿意使用宏并使用所有设置,使用 decltype 的快捷方式宏是否就足够了?比如:

#define T_FOR_EACH( begin_, end_, var_, func_ ) \
  for_each( (begin_), (end_), [](decltype(*begin_) var_) func_ )

那么你可以:

T_FOR_EACH(x.begin(), x.end(), &f, f->f(); );

我没有在定义中为变量添加&amp; 的原因是,使用这种格式仍然可以指定您想要的所有类型说明符以及它是引用还是副本。

如果语法错误,请原谅,我没有 C++11 编译器,我可以用它来测试任何一个,这只是一个想法。

【讨论】:

【参考方案6】:

这样就编译好了

#define FOR_EACH_IN(iter__, list__, ...) \
    std::for_each(list__.begin(), list__.end(), [&](decltype(*list__.begin()) iter__) __VA_ARGS__)

给定:

auto DoSomethingTo = [](T AnElement) /* insert profitable activity */ ;
std::array<T, n> AnArray;

你可以这样做:

FOR_EACH_IN(AnElement, AnArray,

    DoSomethingTo(AnElement);
);

我内心的 javascript ***者对它的外观感到兴奋,但作为 C++ 编码器,我很害怕想出这样的东西。它可能会触发大量静态分析和 linter,而且当出现问题时,我希望您喜欢调试宏。

但是,嘿,它很干净。

【讨论】:

以上是关于在 C++11 中实现干净的 lambda 函数的主要内容,如果未能解决你的问题,请参考以下文章

如何以干净的方式在 Spring Data Redis 中实现事务?

编译器如何实现lambda表达式?

如何使用lambda表达式捕获局部变量?

在 C++11 中实现 boost::barrier

在 Active Record 风格的 ORM 中实现预先加载的干净方法?

以干净的方式在 Asp.Net Core API 中实现 ProblemDetails 的最佳方法