Почему [=] {} имеет захват лямбды?

На интуитивном уровне имеет смысл, что лямбда, которая должна нести какое-либо состояние (через ссылку или иначе), должна быть чистой конвертируемой в указатель голой функции. Тем не менее, я был недавно удивлен, увидев следующие сбои в 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]:

  • лямбда-выражение:
    • составная заявка лямбда-интродуктора
    • ...
  • лямбда-интродуктор:
    • [ lambda-capture opt ]
  • ...

и в § 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
}