Подтвердить что ты не робот

Общая лямбда с функцией std:: не фиксирует переменные

Я пытаюсь использовать общую лямбду С++ 14, но столкнулся с проблемой std:: function.

#include <iostream>
#include <functional>

int main()
{
    const int a = 2;
    std::function<void(int)> f = [&](auto b) { std::cout << a << ", " << b << std::endl; };
    f(3);
}

Это не скомпилируется с сообщением об ошибке, в котором говорится, что error: ‘a’ was not declared in this scope.

Он работает, если я изменяю его на (int b).

Это ошибка? или я чего-то не хватает?

Версия GCC, которую я использую, - 4.9.2.

4b9b3361

Ответ 1

Я могу воспроизвести это, если не сделаю одно из следующих действий:

  • удалить const из a
  • name a в списке захвата
  • изменить std::function<void(int)> на auto
  • сделать лямбда не общим, изменив auto b на int b
  • используйте Clang (например, v3.5.0)

Я считаю, что это ошибка компилятора, связанная с оптимизацией, и неспособность обнаружить odr-использование в общей лямбда (хотя интересно, что установка -O0 не влияет). Это может быть связано с ошибка 61814, но я не думаю, что это одно и то же, поэтому:

Я поднял его как ошибка GCC 64791.

  • (Обновление: эта ошибка с тех пор была отмечена как исправленная в GCC 5.0.)

Разумеется, я не могу найти ничего очевидного в формулировке С++ 14, которая должна запрещать ваш код, хотя в новой формулировке С++ 14 очень мало "очевидных".: (


[C++14: 5.1.2/6]: [..] Для общей лямбда без лямбда-захвата тип замыкания имеет открытый не виртуальный не явный шаблон функции преобразования const для указателя на функцию. Шаблон функции преобразования имеет тот же изобретенный список параметров шаблона, и указатель на функцию имеет те же типы параметров, что и шаблон оператора вызова функции. [..]

[C++14: 5.1.2/12]: Лямбда-выражение с ассоциированным захватом-умолчанию, которое явно не фиксирует эту или переменную с автоматической продолжительностью хранения (это исключает любое идентификатор-выражение, которое, как было установлено, ссылается на связанный с init-capture, статический элемент данных), называется неявным захватом объекта (т.е. this или переменной), если составной оператор:

  • odr использует (3.2) объект или
  • обозначает объект в потенциально вычисленном выражении (3.2), где охватывающее полное выражение зависит от общего параметра лямбда, объявленного в пределах области охвата лямбда-выражения.

[Пример:

void f(int, const int (&)[2] = {}) { } // #1
void f(const int&, const int (&)[1]) { } // #2
void test() {
  const int x = 17;
  auto g = [](auto a) {
    f(x); // OK: calls #1, does not capture x
  };

  auto g2 = [=](auto a) {
    int selector[sizeof(a) == 1 ? 1 : 2]{};
    f(x, selector); // OK: is a dependent expression, so captures x
  };
}

-end example] Все такие неявно захваченные объекты должны быть объявлены в пределах охвата лямбда-выражения. [Примечание. Неявный захват объекта вложенным лямбда-выражением может вызвать его неявный захват с помощью содержащего лямбда-выражения (см. Ниже). Неявное использование odr этого может привести к неявному захвату. -end note]

[C++14: 5.1.2/13]: Объект захватывается, если он зафиксирован явно или неявно. Объект, захваченный лямбда-выражением, используется odr (3.2) в области, содержащей лямбда-выражение. [..]

Ответ 2

int main() {
    const int a = 2;
    auto f = [&](auto b) { std::cout << a << ", " << b << std::endl; };
    f(3);
}

Не знаю, должно ли оно работать с std::function, но это работает точно.

Дальнейшие исследования:

Я создал класс, максимально похожий на лямбда:

class Functor {
private:
  int const x;

public:
  Functor() : x{24} {}
  auto operator()(int b) const -> void { cout << x << " " << b << endl; }
};


std::function<auto(int)->void> f2 = Functor{};
f2(3); // <- this works

Это говорит о том, что ваш пример должен сработать. После того, как все lambdas одинаковы в поведении с объектом, у которого есть перегрузка operator() и поля для захваченных переменных.

Если мы изменим класс, чтобы перейти к части auto:

Это не работает:

class Functor {
private:
  int const x;

public:
  Functor() : x{24} {}
  auto operator()(auto b) const -> void { cout << x << " " << b << endl; }
};

std::function<auto(int)->void> f2 = Functor{}; // <-- doesn't work

Однако это работает:

class Functor {
private:
  int const x;

public:
  Functor() : x{24} {}
  template <class T>
  auto operator()(T b) const -> void { cout << x << " " << b << endl; }
};

std::function<auto(int)->void> f2 = Functor{}; // <-- this works

Скорее всего, это связано с использованием auto в качестве параметра лямбда/функций, новой для С++ 14, поэтому, скорее всего, без зрелой реализации.