Lambda捕获作为const参考吗?


166

是否可以通过const引用在Lambda表达式中捕获?

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

#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";

    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引用捕获非const对象?(2015年8月


您的lambda看起来应该不是[&, &best_string](string const s) { ...}吗?
erjot

3
确实不一致。当您有大型const对象应该在lambda函数中访问但不能修改时,“ const&”将非常有用
sergtk 2012年

看代码。您可以使用两个参数lambda并将第二个参数绑定为const引用。虽然要付出代价。
亚历克斯

1
看起来在C ++ 11中这是不可能的。但是也许我们可以针对C ++ 14更新此问题-是否有扩展名允许这样做?C ++ 14广义lambda捕获?
亚伦·麦克戴德

Answers:


127

const 截至n3092为止,它不在捕获语法中:

capture:
  identifier
  & identifier
  this

文本仅提及按拷贝捕获和按引用捕获,没有提及任何类型的常量性。

对我来说,这似乎是一个疏忽,但我并没有非常严格地遵循标准化过程。


47
我只是将错误跟踪回溯到从捕获中被修改的变量,该变量是可变的,但应该是const。或更准确地说,如果捕获变量为const,则编译器将在程序员上强制执行正确的行为。如果支持语法会很好[&mutableVar, const &constVar]
肖恩

看来这在C ++ 14中应该可行,但我无法使其正常工作。有什么建议?
亚伦·麦克戴德

37
常量是从捕获的变量继承的。因此,如果要捕获aconst,请const auto &b = a;在lambda之前声明并捕获b
StenSoft

7
@StenSoft Bleargh。显然,这在通过引用捕获成员变量时不适用:函数[&foo = this->foo]内部const给我一个错误,指出捕获本身会丢弃限定符。我想这可能是GCC 5.1中的错误。
凯尔·史兰德

119

使用static_cast/ const_cast

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

演示


使用std::as_const

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

演示2


另外,也许应该将其编辑为已接受的答案?无论哪种方式,都应该有一个涵盖c ++ 11和c ++ 14的好答案。虽然,我猜想有人可能会争辩说,在未来几年中c ++ 14将对每个人都足够好
Aaron McDaid 2015年

12
@AaronMcDaid const_cast可以无条件地将volatile对象更改为const对象(当要求转换为时const),因此,为了增加约束,我更喜欢static_cast
Piotr Skotnicki

1
另一方面,@ PiotrSkotnicki,static_cast如果您输入的类型不正确,则以const引用可能会默默地创建一个临时文件
MM

24
@MM &basic_string = std::as_const(best_string)应该解决所有问题
Piotr

14
@PiotrSkotnicki除了问题之外,这是一种可怕的方式来编写应该const& best_string
凯尔·斯特兰德

12

我认为捕获部分不应指定const,作为捕获手段,它仅需要一种访问外部作用域变量的方法。

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

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

lambda函数是const(无法在其范围内更改值),因此,当您按值捕获变量时,该变量无法更改,但引用不在lambda范围内。


1
@Amarnath Balasubramani:我的观点是,我认为不需要在lambda捕获部分中指定const引用,为什么在这里应该有一个const变量而不是在另一个地方const(如果可能的话,容易出错) )。很高兴看到您的回应。
zhb 2014年

2
如果需要better_string在包含范围内进行修改,则此解决方案将无法使用。捕获为const-ref的用例是当变量需要在包含范围内而不是在lambda内可变时。
乔纳森·沙曼

@JonathanSharman,创建变量的const引用不会花费您任何费用,因此您可以创建一个const string &c_better_string = better_string;并将其愉快地传递给lambda:[&c_better_string]
Steed

@Steed的问题是您要在周围的范围内引入一个额外的变量名。我认为上述Piotr Skotnicki的解决方案是最干净的,因为它可以实现const正确性,同时使可变范围最小。
乔纳森·沙曼

@乔纳森·沙曼(JonathanSharman),在这里我们进入见解之地-最漂亮,最干净的东西是什么。我的观点是,两种解决方案都适合该任务。
骏马

8

我想如果您没有将变量用作函子的参数,则应该使用当前函数的访问级别。如果您认为不应该这样做,则可以将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相同。


1
capture子句仍然best_string只提及。除此之外,GCC 4.5会“成功拒绝”预期的代码。
sellibitze

是的,这会给我带来我想要在技术水平上取得的成果。但是最终,对我的原始问题的答案似乎是“否”。
John Dibling

为什么要使其成为“非lambda”?

因为lambda的性质在于它取决于上下文。如果您不需要特定的上下文,那么这只是制作函子的快速方法。如果函子应该与上下文无关,请将其设为真正的函子。
克莱姆(Klaim)2010年

3
“如果函子应该独立于上下文,则使其成为真正的函子”……并亲吻可能的内联再见?
Andrew Lazarus 2014年

5

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

  • 不要使用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);
}

1
除了对包含范围中的变量所做的更改不会反映在lambda中。它不是一个引用,它只是一个变量,不应重新分配,因为重新分配并不意味着它看起来像什么。
Grault


0

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


1
尽管此链接可以回答问题,但最好在此处包括答案的基本部分,并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。”
Div

好的,我编辑了答案以在此处添加gcc错误描述。
user1448926 '16

如果有的话,这是一个间接的答案。该错误是关于在捕获const时编译器如何失败,因此也许为什么某种方法来解决或解决问题中的问题可能不适用于gcc。
斯坦因

0

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


这是一个非常老的问题:您的答案缺少与您所指的C ++版本有关的上下文。请提供此内容。
ZF007 '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.