Почему [=] {} имеет захват лямбды?
На интуитивном уровне имеет смысл, что лямбда, которая должна нести какое-либо состояние (через ссылку или иначе), должна быть чистой конвертируемой в указатель голой функции. Тем не менее, я был недавно удивлен, увидев следующие сбои в GCC, Clang и MSVC:
int main(int, char *[]) {
void (*fp)() = []{}; // OK
//fp = [=]{}; // XXX - no user defined conversion operator available
//fp = [&]{}; // XXX - same ...
}
Спецификация C++ 17 (или, по крайней мере, видимая публичная версия проекта N4713), относится к пункту 7 § 8.4.5.1 [expr.prim.lambda.closure] к лямбдам с захватами и без них:
Тип замыкания для не-общего лямбда-выражения без лямбда-захвата, ограничения которого (если они есть) удовлетворяются, имеет функцию преобразования для указателя на функцию с C++ языковой связью (10.5), имеющей те же параметры и возвращаемые типы, что и замыкание функции вызова функции....
Однако, изучая формальную грамматику, вы можете увидеть следующее в § 8.4.5 [expr.prim.lambda]:
- лямбда-выражение:
- составная заявка лямбда-интродуктора
- ...
- лямбда-интродуктор:
- ...
и в § 8.4.5.2 [expr.prim.lambda.capture]:
- лямбда-захват:
- Захват по умолчанию
- Захват-лист
- capture-default, capture-list
- capture-default:
Так что все компиляторы на самом деле подчинялись букве закона до ужаса...
Почему язык определяет существование захвата как узкое грамматическое различие в декларации, а не основывает его на том, содержит ли тело ссылки на какое-либо нестатическое/захваченное состояние?
Ответы
Ответ 1
Изменение, которое допускало преобразование, было инициировано национальным комментарием. См. N3052: Преобразование Lambdas в указатели функций, которые ссылаются на комментарий национального органа Великобритании 42:
Лямбда с пустым списком захвата имеет идентичную семантику для регулярного типа функции. Благодаря этому отображению мы получаем эффективный лямбда-тип с известным API, который также совместим с существующей операционной системой и библиотечными функциями C.
и резолюция от N3052
была:
Разрешение: добавьте новый абзац: "Лямбда-выражение с пустым множеством захвата должно быть преобразовано в указатель на тип функции R (P), где R - тип возврата, а P - это список типов параметров лямбда-выражения". Кроме того, может быть полезно: (a) разрешить преобразование в ссылку на функцию и (b) разрешить типы указателей функции extern "C".
...
Добавьте новый абзац после параграфа 5. Цель этого редактирования - получить преобразование указателя замыкания на функцию для лямбда без лямбда-захвата.
Тип замыкания для лямбда-выражения без лямбда-захвата имеет публичную не виртуальную неявную функцию преобразования const, чтобы указатель на функцию, имеющую тот же параметр и возвращаемые типы, что и оператор вызова функции типа закрытия. Значение, возвращаемое этой функцией преобразования, должно быть адресом функции, которая при вызове имеет тот же эффект, что и вызов оператора вызова типа закрытия.
и это то место, где мы находимся сегодня. Обратите внимание на комментарий, указанный пустым списком захвата, и то, что мы имеем сегодня, похоже, соответствует намерению, сформулированному в комментарии.
Похоже, что это было исправление, основанное на комментарии национального органа, и было применено узко.
Ответ 2
Правило, которое вы предлагаете, было бы крайне хрупким, особенно в пре-P0588R1, когда неявные захваты зависели от использования odr.
Рассматривать:
void g(int);
void h(const int&);
int my_min(int, int);
void f(int i) {
const int x = 1, y = i;
[=]{ g(x); }; // no capture, can convert?
[=]{ g(y); }; // captures y
[=]{ h(x); }; // captures x
[=]{ my_min(x, 0); }; // no capture, can convert?
[=]{ std::min(x, 0); }; // captures x
}