C++较短的 lambda 语法

C++ shorter lambda syntax

本文关键字:lambda 语法 C++      更新时间:2023-10-16

我是C++新手,最近来自 Swift。有没有办法获得更短的 lambda 语法?

我有很多台词,例如:

columns = {
    Col(_("Name"), "string", [] (Person *p) {
                                return p->Name();
                              }),
    Col(_("Age"), "int", [] (Person *p) {
                                return p->Age();
                              }),
    Col(_("Bank"), "string", [&banks] (Person *p) {
                            return banks.lookUp(p->Identifier()).Name;
                           }),
    //..etc..
};

某些列需要更长的 lambda,但由于它是编写 lambda 的语法,因此与它本身的内容一样长。

lambda 语法可以减少吗?(通过隐式参数或隐式返回最后一个语句)

例如,在 Swift 中,我可以做这样的事情,它会是一样的:

    Col(_("Age"), "int", { $0.Age() }),

编辑:添加了"银行"列作为更复杂的列的示例。

如果始终调用成员函数,则可以使用 mem_fn

Col(_("Name"), "string", std::mem_fn(&Person::Name)),

mem_fn在传递指向对象类型的指针或引用时起作用。

lambda 语法可以减少吗?

我不这么认为。满足您需求的lambda功能的基本组件包括:

[ capture-list ] ( params ) { body }

您可以使用 C++11 尽可能以最小的形式使用它。

使用 C++14 和宏,您可以非常模仿 javascript (ES6) 中箭头函数的语法。

实际上,通常通过引用捕获所有内容就足够了。参数类型可以设置为 auto,返回类型可以省略。因此,C++14 允许我们的最短版本是:

auto f = [&](auto x, auto y) { return x + y; };

显然,它可以很容易地转换为宏:

#define _L2(a, b, res) [&](auto a, auto b) { return res; }
#define _S2(res) _L2(_0, _1, res)

_L2 宏创建具有两个用户指定参数的 lambda。_S2 宏使用名为 _0_1 的参数创建一个 lambda。最后,我们可以使用此答案按参数数重载宏_L。我不知道如何通过宏推断出_S的参数数量。

现在我们可以写这样的东西:

auto g = _L(x, y, x + y);    //std::plus
auto g = _S2(_0 + _1);       //std::plus

你甚至可以做一些疯狂的事情,比如:

//turns unary accessor function into binary comparator functor
auto compByKey = _L(f, _L(x, y, f(x) < f(y)));
//sort vector<string> by length
sort(arr.begin(), arr.end(), compByKey(_L(x, x.length())));

事实上,语法仍然不像原始的javascript或swift那样清晰,但它要短得多。现在的问题是记住我们定义的所有种类的 lambda =)无论如何,STL库对函数式编程并不友好...

完整的代码可在 ideone 上找到。

与其传递 lambda,不如重新设计Col_,以便它可以接受指向成员的指针并知道如何处理它们(例如,将它们包装在 std::mem_fn 中)。这样你就可以写:

columns = {
    Col(_("Name"), "string", &Person::Name),
    Col(_("Age"), "int", &Person::Age),
    //..etc..
};

如果你有 C++14(如果你来自 swift,你可能会这样做),那么你可以用 auto 替换参数类型。此外,非捕获 lambda 不需要空间(它们等效于函数指针),因此如果您只是预定义它们并在初始化器中使用副本,则不会对性能造成影响。

// this will return the Name of any pointee that has a Name() method
auto get_name = [](auto*p) { return p->Name(); }
auto get_age = [](auto*p) { return p->Age(); }
columns = {
    Col(_("Name"), "string", get_name),
    Col(_("Age"), "int", get_age),
    //..etc..
};

我制作了一个简洁的lambda库来使用宏来做到这一点,前提是您可以使用C++20(v0.1.1支持C++17,但有痛苦的警告)。使用此库,您的代码将是:

columns = {
    Col(_("Name"), "string", [] TL(_1->Name())),
    Col(_("Age"), "int", [] TL(_1->Age())),
    Col(_("Bank"), "string", [&banks] TL(banks.lookUp(_1->Identifier()))),
    //..etc..
};

这个TL宏为您提供了一个类似于 { $0.Age() } Swift 语法的表达式 lambda,允许您使用 _1_2 等访问参数。此外,对于需要的情况,此lambda对SFINAE友好且noexcept友好。

请注意,TL 的 lambda 按值返回,就像您在示例 lambda 中所做的那样。如果您希望 lambda 具有decltype(auto)返回类型,则可以改用TLR宏。

<小时 />

如果你想使用这个库,我建议谨慎;使用宏来改变语言的语法是一个危险的想法,可能会使你的代码难以阅读。

你可以

为此#define一个宏,但它有点黑客和邪恶。

#define GEN_LAMBDA(arg) [](Person *p){ return p->arg();}
columns = {
    Col(_("Name"), "string", GEN_LAMBDA(Name)),
    Col(_("Age"), "int", GEN_LAMBDA(Age)),
    //..etc..
};

您可以通过为每个数据成员定义策略来尝试基于策略的设计方法

enum FieldID {AGE, NAME, ID};
template<FieldID fid> auto get(Person &p);
template<FieldID fid> std::string getType();
template<FieldID fid> std::string getFieldName();
template <> auto get<AGE>(Person &p) {return p.Age();}
template <> auto get<NAME>(Person &p) {return p.Name();}
template <> auto get<ID>(Person &p) {return p.ID();}
template <> std::string getType<AGE>() {return "int";}
template <> std::string getType<NAME>() {return "string";}
template <> std::string getType<ID>() {return "size_t";}
template <> std::string getFieldName<AGE>() {return "Age";}
template <> std::string getFieldName<NAME>() {return "Name";}
template <> std::string getFieldName<ID>() {return "Bank";}

你的代码将看起来像这样

Col(_(getFieldName<AGE>()), getType<AGE>(), get<AGE>(p)

前面的答案中有惊人的版本,我有一个不太令人印象深刻的解决方案,但它很简单并且对我来说效果很好(c ++ 20):

// language: c++
#define LambdaBody(...)                             
    noexcept(noexcept(__VA_ARGS__)) ->decltype(auto) 
    requires requires { __VA_ARGS__; }              
    {                                               
        return __VA_ARGS__;                         
    }
#define Lambda0(...) 
    <typename T = void>() LambdaBody(__VA_ARGS__)
#define Lambda1(_1, ...) 
    ([[maybe_unused]] auto&& _1) LambdaBody(__VA_ARGS__)
#define Lambda2(_1, _2, ...)     
    ([[maybe_unused]] auto&& _1, 
     [[maybe_unused]] auto&& _2) LambdaBody(__VA_ARGS__)
#define Lambda3(_1, _2, _3, ...) 
    ([[maybe_unused]] auto&& _1, 
     [[maybe_unused]] auto&& _2, 
     [[maybe_unused]] auto&& _3) LambdaBody(__VA_ARGS__)
#define Lambda4(_1, _2, _3, _4, ...) 
    ([[maybe_unused]] auto&& _1,     
     [[maybe_unused]] auto&& _2,     
     [[maybe_unused]] auto&& _3,     
     [[maybe_unused]] auto&& _4) LambdaBody(__VA_ARGS__)
// ----------------------------------------------------------
#include <iostream>
int main(int i, char ** argv) {
    auto context = 0;
    auto demo0 = [&] Lambda0(++context);
    auto demo1 = [] Lambda1(x, x + 10);
    auto demo2 = [] Lambda2(x, y, x*y + 10);
    auto demo3 = [] Lambda3(x, y, z, x*y + z);
    std::cout << "demo0 = " << demo0() << "n";
    std::cout << "demo0 = " << demo0() << "n";
    std::cout << "demo1 = " << demo1(5) << "n";
    std::cout << "demo2 = " << demo2(1, 2) << "n";
    std::cout << "demo3 = " << demo3(1, 2, 3) << "n";
}

请参阅编译器资源管理器:https://compiler-explorer.com/z/Gc5aofY6v