Лямбда-захват как постоянная ссылка?

166

Можно ли захватить константной ссылкой в ​​лямбда-выражении?

Я хочу, чтобы назначение, отмеченное ниже, не сработало, например:

#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 захватывать неконстантный объект по константной ссылке? ( Август 2015 г. )

Джон Диблинг
источник
Не следует ли ваш лямбда выглядеть следующим образом : [&, &best_string](string const s) { ...}?
erjot
3
действительно непоследовательный захват. «const &» может быть очень полезен, когда у вас есть большой объект const, к которому следует обращаться, но не изменять его в лямбда-функции
sergtk
глядя на код Вы можете использовать лямбду с двумя параметрами и связать второй как константную ссылку. идет с ценой, хотя.
Алекс
1
Это не возможно в C ++ 11, казалось бы. Но, возможно, мы можем обновить этот вопрос для C ++ 14 - есть ли расширения, которые позволяют это? C ++ 14 обобщенные лямбда-захваты?
Аарон МакДейд,

Ответы:

127

const отсутствует в грамматике для захватов с n3092:

capture:
  identifier
  & identifier
  this

В тексте упоминается только «захват за копией» и «захват по ссылке», и не упоминается никакой константности.

Для меня это похоже на недосмотр, но я не очень внимательно следил за процессом стандартизации.

Стив М
источник
47
Я только что отследил ошибку в изменяемой переменной из перехвата, которая была изменяемой, но должна была быть const. Или, более правильно, если бы переменная захвата была const, компилятор принудительно установил бы правильное поведение на программиста. Было бы хорошо, если бы синтаксис поддерживал [&mutableVar, const &constVar].
Шон
Кажется, что это должно быть возможно с C ++ 14, но я не могу заставить его работать. Какие-либо предложения?
Аарон МакДейд
38
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
};

ДЕМО 2

Петр Скотницкий
источник
Кроме того, возможно это должно быть отредактировано в принятый ответ? В любом случае, должен быть один хороший ответ, который охватывает как c ++ 11, так и c ++ 14. Хотя, я думаю, можно утверждать, что c ++ 14 будет достаточно хорош для всех в ближайшие годы
Аарон МакДейд
12
@AaronMcDaid const_castможет безоговорочно изменить изменчивый объект на постоянный объект (когда его просят привести к const), таким образом, для добавления ограничений, которые я предпочитаюstatic_cast
Piotr Skotnicki
1
@PiotrSkotnicki, с другой стороны, static_castдля сравнения может молча создать временное хранилище, если вы не совсем правильно поняли тип
ММ
24
@MM &basic_string = std::as_const(best_string)должен решить все проблемы
Петр Скотницкий
14
@PiotrSkotnicki За исключением проблемы того, что это отвратительный способ написать что-то, что должно быть так же просто, как const& best_string.
Кайл Стрэнд,
13

Я думаю, что часть захвата не должна указывать const, так как захват означает, что ей нужен только способ доступа к переменной внешней области видимости.

Спецификатор лучше указывать во внешней области видимости.

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

Лямбда-функция является константной (не может изменить значение в своей области видимости), поэтому при захвате переменной по значению переменная не может быть изменена, но ссылка не находится в области действия лямбда-выражения.

ЖБ
источник
1
@Amarnath Balasubramani: Это только мое мнение, я думаю, что нет необходимости указывать ссылку const в части лямбда-захвата, почему здесь должна быть переменная const, а не const в другом месте (если это возможно, она будет подвержена ошибкам) ). в любом случае рад видеть ваш ответ.
ЖБ
2
Если вам нужно изменить better_stringвнутри содержащей области, то это решение не будет работать. Вариант использования для захвата как const-ref - это когда переменная должна быть изменяемой в содержащей области, но не в лямбда-выражении.
Джонатан Шарман
@JonathanSharman, вам ничего не стоит создать константную ссылку на переменную, так что вы можете сделать const string &c_better_string = better_string;и счастливо передать ее [&c_better_string]
Steed
@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;
}

Но это то же самое, что предположить, что ваша лямбда должна быть изолирована от текущей функции, что делает ее не лямбда.

Klaim
источник
1
Пункт захвата все еще упоминает best_stringтолько. Кроме того, GCC 4.5 «успешно отклоняет» код, как предполагалось.
Sellibitze
Да, это дало бы мне результаты, которых я пытался достичь на техническом уровне. В конечном счете, однако, ответ на мой первоначальный вопрос, кажется, «нет».
Джон Диблинг
Почему это делает его "не лямбда"?
Потому что природа лямбды такова, что она зависит от контекста. Если вам не нужен конкретный контекст, тогда это просто быстрый способ создать функтор. Если функтор должен быть независимым от контекста, сделайте его настоящим функтором.
Klaim
3
«Если функтор должен быть независимым от контекста, сделайте его настоящим функтором» ... и поцелуй, возможно, встраиваемый прощай?
Андрей Лазарь
5

Я думаю, у вас есть три разных варианта:

  • не используйте константную ссылку, но используйте захват копии
  • игнорировать тот факт, что это модифицируемо
  • используйте 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);
}
Alex
источник
1
За исключением изменений в переменной в области видимости не будет отражаться в лямбда-выражении. Это не ссылка, это просто переменная, которая не должна быть переназначена, потому что переназначение не будет означать, что это будет означать.
Grault
4

Есть более короткий путь.

Обратите внимание, что перед "best_string" нет амперсанда.

Это будет тип "const std :: reference_wrapper << T >>".

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

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

Сергей Палицин
источник
0

Используйте clang или подождите, пока ошибка gcc не будет устранена: ошибка 70385: лямбда-захват по ссылке на константную ссылку завершается неудачей [ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385 ]

user1448926
источник
1
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными в случае изменения связанной страницы ».
Div
Хорошо, я отредактировал свой ответ, чтобы добавить описание ошибки gcc здесь.
user1448926
Это довольно косвенный ответ на вопрос, если таковой имеется. Ошибка заключается в том, что компилятор не работает при захвате чего-либо const, поэтому, возможно, почему какой-то способ решения или обхода проблемы в вопросе может не работать с gcc.
Штейн
0

Использование const просто приведет к тому, что алгоритм ampersand установит строку в исходное значение. Другими словами, лямбда не будет определять себя как параметр функции, хотя окружающая область будет иметь дополнительную переменную ... Без ее определения тем не менее, она не будет определять строку как типичную [&, & best_string] (string const s). Поэтому , скорее всего, лучше, если мы просто оставим ее при этом, пытаясь захватить ссылку.

Говорит
источник
Это очень старый вопрос: в вашем ответе отсутствует контекст, связанный с той версией C ++, на которую вы ссылаетесь. Пожалуйста, предоставьте этот контент.
ZF007