使用lambda函数定义运算符

syn*_*gma 0 c++ lambda operator-overloading c++11

考虑这个例子:

struct A {
    int val;
    A(int val) : val(val){};

    A operator+(const A& a1) {
        return (+[](const A& a1, const A& a2) -> A {
            return A(a1.val + a2.val);
        })(*this, a1);
    };
};

int main(int argc, char* argv[]) {
    A a(14);
    A b(12);
    auto c = a + b;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我有两个问题:

  1. 在这种情况下,编译器是否会优化lambda的使用(这只是包装operator+或者是否会有任何开销?
  2. 使用lambda函数编写运算符是否有更简单的语法?

Yak*_*ont 9

代码的某些部分似乎毫无意义.我会仔细检查它们并将它们逐个剥离.

A operator+(const A& a1) {
    return (+[](const A& a1, const A& a2) -> A {
        return A(a1.val + a2.val);
    })(*this, a1);
};
Run Code Online (Sandbox Code Playgroud)

首先,+[]简单地将它不必要地衰减到函数指针.除了可能混淆优化器之外,这里没什么用处.我们也可以摆脱()s:

A operator+(const A& a1) {
    return [](const A& a1, const A& a2) -> A {
        return A(a1.val + a2.val);
    }(*this, a1);
};
Run Code Online (Sandbox Code Playgroud)

现在,该->A部分是噪音,因为它可以推断出返回类型:

A operator+(const A& a1) {
    return [](const A& a1, const A& a2) {
        return A(a1.val + a2.val);
    }(*this, a1);
};
Run Code Online (Sandbox Code Playgroud)

接下来,为什么使用会员operator++是对称的,通过使用Koenig风格的运算符,这变得更加明显:

friend A operator+(const A& a1, const A& a2) {
    return [](const A& a1, const A& a2) {
        return A(a1.val + a2.val);
    }(a1, a2);
};
Run Code Online (Sandbox Code Playgroud)

它摆脱了你引入的编号混淆(a1在一个范围内a2的另一个范围内,并且a1引入了一个引用的新变量*this).

最后,lambda在这一点上什么都不做:

friend A operator+(const A& a1, const A& a2) {
  return A(a1.val + a2.val);
};
Run Code Online (Sandbox Code Playgroud)

并删除它导致更清晰的代码.


现在关于你的问题.

在您完成这些简化步骤时,编译器更有可能优化lambda.我怀疑你做的最糟糕的事情是一元+,它将lambda转换为一个函数指针:我知道gcc擅长内联函数指针,最后我检查了MSVC是不好的.但是,每个编译器都擅长内联对无状态lambda的调用.

在调用时,被调用的方法对于编译器而言是类型系统所熟知的,因此不必对函数指针的上帝进行分析.数据被复制到随后使用的参数中,通常在一个小函数中.这很容易内联.

即使您有适度的捕获要求,只要您不键入擦除或复制lambda,就会有一堆本地引用或lambda主体中使用的局部变量的副本.易于内联.

现在,我的答案的第一部分中的每个简化步骤使代码更简单,直到最后,当lambda消失并且"优化"的唯一事情是一个简单的RVO(你必须用编译器标志来抑制它以防止它发生的事情),也就是Areturn声明中忽略了临时性.

如果你A的结果中有一个隐式构造函数.val+.val,你甚至不需要Areturn语句中.


没有一种简单的方法可以用lambdas来编写运算符.你需要在某个地方存储所述lambda,然后将它operator+(或其他)粘合到它上面,这两个步骤将需要更多的代码,而不仅仅是直接将体注入operator+.

我可以做一些花哨的decltype和宏废话,让你将一个全局lambda绑定到operator+某个类的给定体(使用CRTP和traits类),但调用"更简单"不仅仅是一个延伸.

它可以让你做一些事情:

const auto print_via_to_string = [](std::ostream& os, auto&& val){
  using std::to_string;
  os << to_string(decltype(val)(val));
};

struct Foo { /* ... */ };

BIND_OPERATOR( <<, std::ostream&, Foo const&, print_via_to_string );
Run Code Online (Sandbox Code Playgroud)

或者某些,甚至期望<<被内联.

再次,考虑到这个"更简单"是一个延伸.