在三元运算符中初始化捕获 lambda

Posted

技术标签:

【中文标题】在三元运算符中初始化捕获 lambda【英文标题】:Initializing capturing lambda in ternary operator 【发布时间】:2018-07-02 00:17:03 【问题描述】:

我想通过以下方式创建一个 lambda:

auto l1 = condition ?
          []() return true;  :
          [number]() return number == 123; ;

但是,我得到了错误:

operands to ?: have different types ‘main()::<lambda()>’ and ‘main()::<lambda()>’

显然,类型似乎相同。我想,仅在一个 lambda 表达式中捕获 number 可能是个问题,但我得到了同样的错误:

//check if capturing number in both lambdas would help
auto l2 = condition ?
          [number]() return true;  :
          [number]() return number == 123; ;
//maybe the first lambda capture was optimised out? let's make sure:
auto l3 = condition ?
          [number]() return number != 123;  :
          [number]() return number == 123; ;

我知道我可以通过其他方式(如下)做到这一点,但我想知道这种行为的原因是什么。它使用 GCC6.3.1 编译,启用了 C++14。

//compiles      
auto l4 = condition ?
          [](const int) return true;  :
          [](const int number) return number == 123; ;

【问题讨论】:

【参考方案1】:

每个lambda expression 都有唯一的类型(即闭包类型,它是唯一的未命名非联合非聚合类类型),即使具有相同的签名和函数体;编译器只是无法为auto声明的变量推导出ternary conditional operator的公共类型,这两种闭包类型根本不相关。

您可以改用std::function。例如

std::function<bool()> l1;
if (condition)
     l1 = []() return true; ;
else
     l1 = [number]() return number == 123; ;

对于l4,请注意捕获列表为空的 lambda 表达式可以隐式转换为相应的函数指针。在这种情况下,它们都可以转换为相同的函数指针类型(即bool(*)(int)),然后可以推导出为三元条件运算符的公共类型和l4的类型。

【讨论】:

那为什么 l4 会编译呢? @knopers8 因为非捕获 lambda 隐式转换为它们的通用函数指针类型(例如 bool(*)())... 因为它们具有相同的签名。仅仅因为第一个选项中的参数没有命名并不意味着它不同。 @MarošBeťko 2 个具有相同签名的 lambda,即使 2 个具有相同主体的 lambda 也具有不同的类型。它的工作原理由 Massimiliano 指定 @knopers8 所有的 cmets 都是正确的。解释也添加到答案中。【参考方案2】:

如果你真的想使用条件运算符,你可以这样做:

auto l1 = condition
          ? std::function<bool()>[]() return true; 
          : std::function<bool()>[number]() return number == 123; ;

在 C++17 中,这可以通过 Class template argument deduction 来简化:

auto l1 = condition
          ? std::function[]() return true; 
          : std::function[number]() return number == 123; ;

【讨论】:

与 lambdas 相比,不使用 std::function 会带来一些开销吗? @Yola 是的。这就是类型擦除的代价。 @bolov 什么是类型擦除 @Kapil This 可能会帮助你。【参考方案3】:

C++ 中的 lambda 是实现函子协定的本地类的实例。我的意思是,operator() 等。这些类是不相关的,并且有不同的类型。

你的代码相当于

struct l1 
  bool operator () const 
    return true;
  
;

struct l2 
  private int number_:
  l2(int number): number_(number)

  bool operator () const 
    return number_ == 123;
  
;

int number = ???
auto l3 = condition ? l1 : l2(number);

【讨论】:

以上是关于在三元运算符中初始化捕获 lambda的主要内容,如果未能解决你的问题,请参考以下文章

在 Lambda 中使用三元运算符的替代方法

三元运算和lambda

Python-三元运算符和lambda表达式

Python:拥有三元运算符的有效方法[重复]

三元运算符不适用于 lambda 函数

使用 Python 的三元运算符与 lambda 组合的意外输出