lambda 的 return 值的规范不起作用

Specification of lambda's return value doesn't work

我有问题。 函数“__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)的规范。 在有或没有 return 值的规范的情况下,它都不起作用。

它说:

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

建议:将 __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;
};

否则编译器无法在 __sub().

的主体内使用相同的 __sub() 推断 (auto) __sub() 的类型

我不同意这是一个先有鸡还是先有蛋的问题,或者至少是一个可以解决的问题的说法,相反我建议这是语言的一个怪癖,因为你几乎可以完成手工做同样的事情。

为了稍微简化讨论,举一个常见的递归例子,阶乘(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++ classes 不一致,后者不能包含自身的副本,即使否则为空。因此,必须通过引用捕获 (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 形式也行。 factorial中的auto在推导之前,很一个不完整的类型。 C++ 允许对不完整类型的引用和指针,包括对 class 或包含它们的结构的引用和指针。而 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);
};

希望对您有所帮助!