使用 lambda 函数定义运算符
Defining operators using lambda functions
考虑这个例子:
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;
}
我有两个问题:
- 在这种情况下,编译器是否会优化 lambda 的使用(它只是包装到
operator+
中,还是会有任何开销? - 使用 lambda 函数编写运算符是否有更简单的语法?
代码的某些部分似乎毫无意义。 我将逐一浏览它们并将它们剥离出来。
A operator+(const A& a1) {
return (+[](const A& a1, const A& a2) -> A {
return A(a1.val + a2.val);
})(*this, a1);
};
首先,+[]
只是将其不必要地衰减到函数指针。 这在这里没有任何用处,除了可能混淆优化器。 我们也可以摆脱()
A operator+(const A& a1) {
return [](const A& a1, const A& a2) -> A {
return A(a1.val + a2.val);
}(*this, a1);
};
现在,->A
部分是噪声,因为它可以推断出返回类型:
A operator+(const A& a1) {
return [](const A& a1, const A& a2) {
return A(a1.val + a2.val);
}(*this, a1);
};
接下来,为什么要使用成员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);
};
这摆脱了您引入的编号混淆(其中一个作用域中的a1
在另一个作用域中a2
,并引入了一个名为 a1
的新变量,该变量引用了*this
)。
最后,lambda 此时不执行任何操作:
friend A operator+(const A& a1, const A& a2) {
return A(a1.val + a2.val);
};
删除它会导致更清晰的代码。
现在关于你的问题。
编译器更有可能在您完成这些简化步骤时优化 lambda。 我怀疑你做的最糟糕的事情是一元+
,它将lambda转换为函数指针:我知道gcc擅长内联函数指针,最后我检查了MSVC不擅长它。 但是,每个编译器都擅长内联对无状态 lambda 的调用。
在调用点,类型系统对编译器所调用的方法是众所周知的,因此无需对函数指针的提供进行分析。 数据被复制到参数然后使用,通常在一个小函数中。 这很容易内联。
即使您有适度的捕获要求,只要您不键入擦除或复制 lambda,您就会在 lambda 正文中使用一堆本地引用或局部变量的副本。 易于内联。
现在,我答案的第一部分中的每个简化步骤都使代码更简单,直到最后,当lambda消失并且唯一要"优化"的是一个简单的RVO(您必须使用编译器标志抑制以防止它发生),也就是省略return
语句中的临时A
。
如果你的A
有一个来自.val+.val
结果的隐式构造函数,你甚至不需要return
语句中的A
。
没有一种简单的方法可以根据lambda编写运算符。 您需要将所述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 );
或诸如此类,甚至希望<<
是内联的。
同样,考虑这种"更简单"是一个延伸。
- C++映射:具有自定义类的运算符[]不起作用(总是返回0)
- 有没有一种方法可以通过"typedef"为重新定义的基本类型定义特征和强制转换运算符
- 如何正确实现和访问运算符的各种自定义枚举器
- 自定义先决条件对移动分配运算符有效吗
- 在运算符重载定义中使用成员函数(const错误)
- g++用户定义的动态链接库上的全局new和delete运算符
- 一元"运算符"未在C++中定义
- STL按客户"<"运算符对向量进行排序。为什么要将"<"运算符定义为 const?
- 如何将运算符定义从头文件添加到现有结构?
- 在使用具有相等运算符定义的抽象类时,如何定义父类以减少代码重复?
- 当我们没有将运算符+定义为朋友时?C++
- 没有返回值的c++类成员运算符定义
- 用指向对象的指针和c++中的new运算符定义对象
- <T> 仅为具有运算符/定义的此类 T 创建模板
- 基类中的运算符定义
- C++:不同名称空间中的多个运算符定义
- 运算符定义不能正常工作
- 类运算符'='定义
- 为什么VS不为逻辑运算符定义可选的令牌?
- std::equal_range 不适用于具有运算符<定义的结构