【问题标题】:Lambda capture as const reference?Lambda 捕获作为 const 参考?
【发布时间】:2011-04-15 22:20:30
【问题描述】:

是否可以通过 lambda 表达式中的const 引用来捕获?

我希望下面标记的作业失败,例如:

#include <algorithm>
#include <string>

using namespace std;

int main()
{
    string strings[] = 
    {
        "hello",
        "world"
    };
    static const size_t num_strings = sizeof(strings)/sizeof(strings[0]);

    string best_string = "foo";

    for_each( &strings[0], &strings[num_strings], [&best_string](const string& s)
      {
        best_string = s; // this should fail
      }
    );
return 0;
}

更新: 由于这是一个老问题,如果 C++14 中有工具可以帮助解决这个问题,更新它可能会很好。 C++14 中的扩展是否允许我们通过 const 引用捕获非常量对象? (2015 年 8 月

【问题讨论】:

  • 你的 lambda 不应该像:[&amp;, &amp;best_string](string const s) { ...}?
  • 捕获确实不一致。当你有一个应该访问但不能在 lambda 函数中修改的大型 const 对象时,“const &”会非常有用
  • 查看代码。您可以使用两个参数 lambda 并将第二个参数绑定为 const 引用。不过是有代价的。
  • 这在 C++11 中似乎是不可能的。但也许我们可以为 C++14 更新这个问题——是否有允许这样做的扩展? C++14 广义 lambda 捕获?

标签: c++ c++11 lambda c++14


【解决方案1】:

中使用static_cast / const_cast

[&best_string = static_cast<const std::string&>(best_string)](const string& s)
{
    best_string = s; // fails
};

DEMO


中使用std::as_const

[&best_string = std::as_const(best_string)](const string& s)
{
    best_string = s; // fails
};

DEMO 2

【讨论】:

  • @AaronMcDaid const_cast 可以无条件地将 volatile 对象更改为 const 对象(当要求转换为 const 时),因此,对于添加约束,我更喜欢 static_cast
  • @PiotrSkotnicki 另一方面,static_cast 到 const 引用可能会默默地创建一个临时的,如果你没有得到完全正确的类型
  • @M.M &amp;basic_string = std::as_const(best_string) 应该可以解决所有问题
  • @PiotrSkotnicki 除了这是一种可怕的方式来编写应该const&amp; best_string一样简单的东西。
  • 您实际上可以从c++14 开始写best_string = std::cref(best_string)。有关说明,请参阅 Sergey's answer,或 this example
【解决方案2】:

const 不在 n3092 的捕获语法中:

capture:
  identifier
  & identifier
  this

文中仅提及 capture-by-copy 和 capture-by-reference,并没有提及任何类型的 const-ness。

对我来说感觉像是一个疏忽,但我没有非常密切地关注标准化过程。

【讨论】:

  • 我刚刚追踪到一个错误,该错误是从捕获中修改的变量,该变量是可变的,但应该是const。或者更准确地说,如果捕获变量是const,编译器会对程序员强制执行正确的行为。如果语法支持[&amp;mutableVar, const &amp;constVar]就好了。
  • C++14 似乎应该可以做到这一点,但我无法让它工作。有什么建议吗?
  • 常量继承自捕获的变量。因此,如果您想将a 捕获为const,请在lambda 之前声明const auto &amp;b = a; 并捕获b
  • @StenSoft Bleargh。除非显然这在通过引用捕获成员变量时不适用:[&amp;foo = this-&gt;foo] 内部 const 函数给我一个错误,指出 捕获本身 丢弃限定符。不过,我想这可能是 GCC 5.1 中的一个错误。
【解决方案3】:

我认为捕获部分不应该指定const,作为捕获手段,它只需要一种访问外部范围变量的方式。

说明符最好在外部范围内指定。

const string better_string = "XXX";
[&better_string](string s) {
    better_string = s;    // error: read-only area.
}

lambda函数是const(can't change value in its scope),所以当你按值捕获变量时,变量不能改变,但是引用不在 lambda 范围内。

【讨论】:

  • @Amarnath Balasubramani:这只是我的看法,我认为没有必要在 lambda 捕获部分指定 const 引用,为什么这里应该有一个变量 const 而不是另一个地方的 const (如果那样的话可能,这将容易出错)。无论如何,很高兴看到您的回复。
  • 如果您需要在包含范围内修改better_string,则此解决方案将不起作用。捕获为 const-ref 的用例是当变量需要在包含范围内可变但在 lambda 内不可变时。
  • @JonathanSharman,创建对变量的 const 引用不会花费您任何费用,因此您可以创建一个 const string &amp;c_better_string = better_string; 并愉快地将其传递给 lambda:[&amp;c_better_string]
  • @Steed 问题在于你在周围的作用域中引入了一个额外的变量名。我认为 Piotr Skotnicki 的上述解决方案是最干净的,因为它在保持变量范围最小化的同时实现了 const 正确性。
  • @JonathanSharman,在这里我们进入了意见的土地 - 什么是最漂亮的,或最干净的,或其他什么。我的观点是这两种解决方案都适合这项任务。
【解决方案4】:

我猜如果你不使用变量作为函子的参数,那么你应该使用当前函数的访问级别。如果您认为不应该这样做,请将您的 lambda 与此函数分开,它不是它的一部分。

无论如何,您可以通过使用另一个 const 引用轻松实现您想要的相同功能:

#include <cstdlib>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

int main()
{
    string strings[] = 
    {
        "hello",
        "world"
    };
    static const size_t num_strings = sizeof(strings)/sizeof(strings[0]);

    string best_string = "foo";
    const string& string_processed = best_string;

    for_each( &strings[0], &strings[num_strings], [&string_processed]  (const string& s)  -> void 
    {
        string_processed = s;    // this should fail
    }
    );
    return 0;
}

但这与假设您的 lambda 必须与当前函数隔离,使其成为非 lambda 相同。

【讨论】:

  • 捕获子句仍然只提到best_string。除此之外,GCC 4.5 按预期“成功拒绝”了代码。
  • 是的,这会给我在技术层面上试图达到的结果。然而,最终,我最初的问题的答案似乎是“不”。
  • 为什么要让它成为“非 lambda”?
  • 因为 lambda 的本质是它依赖于上下文。如果您不需要特定的上下文,那么这只是制作仿函数的一种快速方法。如果函子应该是上下文无关的,那就让它成为一个真正的函子。
  • “如果函子应该是上下文无关的,那就让它成为一个真正的函子” ... 和可能的内联告别?
【解决方案5】:

有一个更短的方法。

请注意,“best_string”之前没有和号。

它将是const std::reference_wrapper&lt;T&gt; 类型。

[best_string = std::cref(best_string)](const string& s)
{
    best_string = s; // fails
};

http://coliru.stacked-crooked.com/a/0e54d6f9441e6867

【讨论】:

【解决方案6】:

我认为你有三种不同的选择:

  • 不要使用 const 引用,而是使用复制捕获
  • 忽略它是可修改的事实
  • 使用 std::bind 绑定具有 const 引用的二进制函数的一个参数。

使用副本

关于带有复制捕获的 lambda 的有趣之处在于,它们实际上是只读的,因此可以完全按照您的意愿进行操作。

int main() {
  int a = 5;
  [a](){ a = 7; }(); // Compiler error!
}

使用 std::bind

std::bind 减少了函数的数量。但是请注意,这可能/将导致通过函数指针进行间接函数调用。

int main() {
  int a = 5;
  std::function<int ()> f2 = std::bind( [](const int &a){return a;}, a);
}

【讨论】:

  • 除非对包含范围内的变量的更改不会反映在 lambda 中。它不是参考,它只是一个不应该重新分配的变量,因为重新分配并不意味着它看起来意味着什么。
【解决方案7】:

使用 clang 或等到这个 gcc 错误修复: 错误 70385:通过引用 const 引用捕获 Lambda 失败 [https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385]

【讨论】:

  • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。”
  • 好的,我编辑了我的答案以在此处添加 gcc 错误描述。
  • 这是一个相当间接的回答问题,如果有的话。该错误是关于编译器在捕获 const 时如何失败,所以也许为什么解决或解决问题中的问题的某种方法可能不适用于 gcc。
  • 对于任何读者来说,这似乎从 gcc 7.1 开始修复:godbolt.org/z/c8MnnY7xY
【解决方案8】:

使用 const 只会让算法 & 将字符串设置为其原始值, 换句话说,lambda 不会真正将自己定义为函数的参数,尽管周围的范围会有一个额外的变量...... 如果不定义它,它不会将字符串定义为典型的 [&, &best_string](string const s) 因此,如果我们只保留它,尝试捕获引用,它很可能会更好。

【讨论】:

  • 这是一个非常古老的问题:您的答案缺少与您所指的 C++ 版本相关的上下文。请提供此内容。
猜你喜欢
  • 2015-12-29
  • 1970-01-01
  • 1970-01-01
  • 2019-12-28
  • 2018-04-04
  • 1970-01-01
  • 2011-02-19
  • 2019-03-05
相关资源
最近更新 更多