指定lambda的返回值不起作用

时间:2019-06-29 13:18:44

标签: c++ recursion lambda return-value auto

我有问题。 函数“ __sub”解析一个类似"1x + (5y - 2)"的字符串。每次看到“ (”时,它都会调用自己以准确解析括号中的内容。 这是一些伪代码,说明了问题:

auto __sub = [&needed_fn](const char *& iter, char end_at) -> int {
  for (; *iter != end_at; iter++) {
    if () {
      int number = needed_fn(iter);
    } else if (*iter == '(') {
      int sub_result = __sub(iter, ')');
    }
  }
  return 0; // temporarily, as for debugging purposes only needed
};

但这不起作用。最初,没有({-> int)的规范。 无论是否指定返回值,这在两种情况下都不起作用。

它说:

a.cpp: In lambda function:
a.cpp:97:22: error: use of ‘__sub’ before deduction of ‘auto’
 int sub_result = __sub(it, ')');

2 个答案:

答案 0 :(得分:2)

建议:将__sub定义为std::function<int(const char *, char)>

std::function<int(const char * &, char)> __sub;

__sub = [&needed_fn](const char *& iter, char end_at) -> int {
  for (; *iter != end_at; iter++) {
    if ( /* ??? */ ) {
      int number = needed_fn(iter);
    } else if (*iter == '(') {
      int sub_result = __sub(iter, ')');
    }
  return 0;
};

否则,编译器无法在auto主体中使用相同的__sub()来推断__sub()的类型。{p}

答案 1 :(得分:1)

我将不同意这是一个鸡鸡蛋问题,或者至少是一个可解决的问题,并建议这是该语言的怪癖,因为您几乎可以完成同一件事用手。

为稍微简化讨论,请使用一个通用的递归示例,阶乘(godbolt):

auto factorial = [](int n) {
    if (n == 0)
        return 1;
    else
        return n * factorial(n - 1);
};

失败,并显示以下错误:

<source>: In lambda function:
<source>:7:24: error: use of 'factorial' before deduction of 'auto'
    7 |             return n * factorial(n-1);
      |                        ^~~~~~~~~

但是factorial是自动存储持续时间的变量,因此如果不捕获它就不能引用它,并且代码必须不正确而不能捕获。按值捕获没有意义,因为lambda类型将包含其自身的副本。这将与典型的C ++类不一致,即使它们为空,它们也不能包含其自身的副本。因此,必须通过引用(godbolt)来捕获:

auto factorial = [&factorial](int n) {
    if (n == 0)
        return 1;
    else
        return n * factorial(n - 1);
};

我们的代码现在更正确了。编译器怎么说?

<source>:3:24: error: use of 'factorial' before deduction of 'auto'
    3 |     auto factorial = [&factorial](int n) {
      |                        ^~~~~~~~~
<source>: In lambda function:
<source>:7:24: error: use of 'factorial' before deduction of 'auto'
    7 |             return n * factorial(n - 1);
      |                        ^~~~~~~~~

更多错误! Lambda只是函数对象的语法糖,因此让我们退后一步,看看未修饰形式是否会起作用(godbolt):

struct factorial_t
{
    factorial_t& factorial;
    auto operator()(int n) const
    {
        if (n == 0)
            return 1;
        else
            return n * factorial(n - 1);
    }
};

int main()
{
    factorial_t factorial{factorial};
}

那行得通,在一个完美的世界中,也许lambda形式也可以。在推导auto中的factorial之前,它很像 一个不完整的类型。在C ++中允许对不完整类型的引用和指针,包括对包含它们的类或结构的引用和指针。而且lambda引用捕获只是引用或指针。因此,在语言的精神内,这一切都是可能的。另一种语言可以将factorial的类型推导为lambda的类型,而lambda类型是不完整的,即在尝试为lambda类型创建定义之前。

在C ++中,您有几种可能的解决方案。首先,您可以手动编写闭包类型(如the third example一样。)

第二,您可以像其他答案(godbolt)一样清除类型:

std::function<int(int)> factorial = [&factorial](int n) {
    if (n == 0)
        return 1;
    else
        return n * factorial(n - 1);
};

请注意,另一个答案缺少关键的捕获内容。

第三,您可以延迟对类型(godbolt)的需要:

auto factorial = [](int n, auto&& factorial) {
    if (n == 0)
        return 1;
    else
        return n * factorial(n - 1, factorial);
};

通过使调用操作符成为模板来延迟对类型的需求,但以笨拙的用法为代价,例如factorial(4, factorial)。即使使用少量的间接(godbolt),这也是可以克服的:

auto factorial_impl = [](int n, auto&& factorial_impl) {
    if (n == 0)
        return 1;
    else
        return n * factorial_impl(n - 1, factorial_impl);
};

auto factorial = [&factorial_impl](int n) {
    return factorial_impl(n, factorial_impl);
};

希望这会有所帮助!