Захоплення лямбда як посилання на const?


166

Чи можливо зафіксувати через посилання 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";

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

Оновлення: Оскільки це старе питання, можливо, було б корисно оновити його, якщо в С ++ 14 є засоби, які допоможуть у цьому. Чи дозволяють розширення в C ++ 14 нам захоплювати об'єкт, що не містить const, за допомогою посилання const? ( Серпень 2015 р. )


чи не повинен виглядати ваш лямбда [&, &best_string](string const s) { ...}:?
erjot

3
дійсно непослідовне захоплення. "const &" може бути дуже корисним, коли у вас є великий об'єкт const, до якого слід отримати доступ, але не змінювати функцію лямбда
sergtk

дивлячись на код. ви можете використати лямбда з двома параметрами, а другий прив'язати як посилання const. Хоча йде з вартістю.
Олексій

1
Це не можливо в C ++ 11, здавалося б. Але, можливо, ми можемо оновити це питання для C ++ 14 - чи є розширення, які це дозволяють? C ++ 14 узагальнених лямбда захоплює?
Aaron McDaid

Відповіді:


127

const не в граматиці для записів станом на n3092:

capture:
  identifier
  & identifier
  this

У тексті згадується лише захоплення за копією та захоплення за посиланням, а також не згадується про будь-яку конкурентоспроможність.

Мені це здається недоглядом, але я дуже уважно не стежив за процесом стандартизації.


47
Я просто відслідковував помилку назад до змінної, що змінювалася з моменту захоплення, яка була зміною, але мала бути const. Або, правильніше, якби була змінна захоплення const, компілятор застосував би правильну поведінку програміста. Було б добре, якщо синтаксис підтримується [&mutableVar, const &constVar].
Шон

Здається, що це можливо для C ++ 14, але я не можу змусити його працювати. Будь-які пропозиції?
Aaron McDaid

37
Constness успадковується від захопленої змінної. Тож якщо ви хочете захопити aяк const, const auto &b = a;b
заявляйте

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
};

DEMO


В використовуючи std::as_const:

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

DEMO 2


Також, можливо, це слід відредагувати у прийнятій відповіді? У будь-якому випадку має бути одна добра відповідь, яка охоплює як c ++ 11, так і c ++ 14. Хоча, мабуть, можна стверджувати, що c ++ 14 буде достатньо хорошим для всіх протягом найближчих років
Аарон Макдейд

12
@AaronMcDaid const_castможе беззастережно змінити мінливий об'єкт на об'єкт const (коли його попросять передати const), таким чином, для додавання обмежень, які я віддаю перевагуstatic_cast
Piotr Skotnicki

1
@PiotrSkotnicki, з іншого боку, static_castпосилання const може мовчки створити тимчасовий, якщо ви не отримали відповідний тип точно
ММ

24
@MM &basic_string = std::as_const(best_string)повинен вирішити всі проблеми
Пьотр Скотницький

14
@PiotrSkotnicki Окрім проблеми у тому, що бути огидним способом написати щось, що має бути таким же простим, як const& best_string.
Кайл Странд

12

Я думаю, що частина захоплення не повинна вказувати const, оскільки це означає, що вона потребує лише доступу до зовнішньої змінної області.

Специфікатор краще вказати у зовнішній області.

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

Функція лямбда - const (не може змінити значення у своїй області), тому коли ви захоплюєте змінну за значенням, змінну неможливо змінити, але посилання не в області лямбда.


1
@Amarnath Balasubramani: Це лише моя думка, я думаю, що немає потреби вказувати посилання const у частині захоплення лямбда, чому тут має бути змінний const, а не const в іншому місці (якщо це можливо, це буде схильним до помилок ). рада бачити вашу відповідь все одно.
zbb

2
Якщо вам потрібно внести зміни better_stringв область, що містить, це рішення не працюватиме. Випадок використання для захоплення як const-ref - це те, коли змінна повинна бути змінена в області, що містить, але не в межах лямбда.
Джонатан Шарман

@JonathanSharman, це не коштує вам нічого, щоб створити посилання const на змінну, тож ви можете зробити const string &c_better_string = better_string;і з радістю передати його лямбда:[&c_better_string]
Відправлено

@Steed Проблема в тому, що ти вводиш додаткову назву змінної у навколишнє поле. Я думаю, що рішення Петра Скотницького вище є найбільш чітким, оскільки воно забезпечує коректність коректування, зберігаючи мінімальну область змін.
Джонатан Шарман

@JonathanSharman, тут ми входимо в країну думок - яка найкрасивіша, чи найчистіша чи будь-яка інша. Моя думка полягає в тому, що обидва рішення підходять для завдання.
Шлях

8

Я думаю, якщо ви не використовуєте змінну як параметр функтора, то вам слід використовувати рівень доступу поточної функції. Якщо ви думаєте, що не варто, то відокремте лямбда від цієї функції, це не є її частиною.

У будь-якому випадку, ви можете легко досягти того самого, що ви хочете, скориставшись замість іншого посилання на 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;
}

Але це те саме, що припускати, що вашу лямбду потрібно ізолювати від поточної функції, що робить її не лямбда.


1
Застереження про захоплення все ще згадується best_string. Крім цього, GCC 4.5 "успішно відкидає" код, як задумано.
sellibitze

Так, це дало б мені ті результати, яких я намагався досягти на технічному рівні. Зрештою, відповідь на моє первісне запитання здається "ні".
Джон Дайлінг

Чому це зробило б це "не лямбда"?

Тому що природа лямбда полягає в тому, що вона залежить від контексту. Якщо вам не потрібен конкретний контекст, то це просто швидкий спосіб зробити функтор. Якщо функтор повинен бути незалежним від контексту, зробіть його справжнім функтором.
Клайм

3
"Якщо функтор повинен бути незалежним від контексту, зробіть його справжнім функтором" ... і поцілуйте, можливо, прощаючись?
Андрій Лазар

5

Я думаю, у вас є три різні варіанти:

  • не використовуйте посилання const, але використовуйте захоплення копії
  • ігноруйте той факт, що він може змінюватися
  • використовувати std :: bind, щоб прив'язати один аргумент бінарної функції, який має посилання const.

за допомогою копії

Цікава частина лямбдів із захопленнями копій - це те, що вони насправді лише для читання і тому роблять саме те, що ви хочете.

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
За винятком того, що зміна змінної в області, що містить, не відображатиметься в лямбда. Це не посилання, це просто змінна, яку не слід перепризначати, тому що перепризначення не означатиме того, що, здавалося б, означає.
Грауль


0

Використовуйте clang або зачекайте, поки ця помилка gcc не буде виправлена: помилка 70385: Знімання лямбда за посиланням на посилання const не вдається [ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385 ]


1
Хоча це посилання може відповісти на питання, краще включити сюди суттєві частини відповіді та надати посилання для довідки. Відповіді лише на посилання можуть стати недійсними, якщо пов’язана сторінка зміниться. "
Div

Гаразд, я відредагував свою відповідь, щоб додати тут опис помилок gcc.
користувач1448926

Це досить непряма відповідь на питання, якщо воно є. Помилка полягає в тому, як компілятор виходить з ладу під час фіксації чогось const, тому, можливо, чомусь якийсь спосіб вирішити чи вирішити проблему у питанні може не працювати з gcc.
Штейн

0

Використання const просто дозволить алгоритму ampersand встановити рядок у його початкове значення. Іншими словами, лямбда насправді не визначатиме себе як параметр функції, хоча навколишня область буде мати додаткову змінну ... Без визначення цього однак, він не визначає рядок як типовий [&, & best_string] (string const s). Тому , швидше за все, краще, якщо ми просто залишимо його, намагаючись захопити посилання.


Це дуже давнє запитання: у вашій відповіді відсутній контекст, пов’язаний з якою версією C ++ ви посилаєтесь. Надайте цей вміст.
ZF007
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.