Может ли лямбда безопасно вернуть адрес скопированной переменной?

Учитывая следующий пример кода:

int main()
{
    int i;
    auto f = [=]()mutable->int*
    {
            return &i;
    };

    return 0;
}
  • g++ v.4.8.1 предупреждает, что "адрес локальной переменной" я вернулся ".
  • Clang v.3.2 (MacOS Clang) предупреждает, что "адрес памяти стека связанные с локальной переменной "i".
  • Ни VS2012, ни VS2013 RC не предупреждают о чем-либо.

Мое понимание lambdas заключается в том, что компилятор будет генерировать класс функтора. Этот класс функторов будет содержать члены для всех скопированных переменных (i в примере). Я считаю, что в контексте моего кода, пока существует f, можно вернуть адрес одного из его членов. Мне кажется, что все компиляторы ошибались. Я думаю, что предупреждение об использовании адреса f member i после f выходит за рамки допустимо, но предупреждения о "локальной переменной i" неверны/вводятся в заблуждение. Я прав?

Ответы

Ответ 1

Да, ты прав. Оператор & применяется к элементу, а не локальному объекту.

Демонстрация прост: просто измените свой пример, чтобы вывести адреса.

#include <iostream>

int main() {
    int i;
    std::cout << & i << '\n';

    std::cout << [=]() mutable -> int * {
        return & i;
    } () << '\n';
}

http://ideone.com/OqsDyg

Кстати, это компилируется без предупреждений в -Wall в GCC 4.9.

Ответ 2

Некоторая терминология:

  • = или & внутри [&](){ /*..*/ } называется значением по умолчанию для захвата.
  • odr-использование переменной примерно означает, что переменная не появляется в выражении с отбрасыванием-значением (например, (void)some_variable или int x = some_variable, 5;), и оно не встречается в постоянном выражении.
  • составной оператор - это "функциональный блок" { statement }
  • имя переменной является id-выражением

[expr.prim.lambda]/3

Тип лямбда-выражения (который также является типом объекта замыкания) - это уникальный, неназванный тип неединичного класса, называемый типом замыкания, свойства которого описаны ниже.

/11

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

Следовательно, i неявно фиксируется.

/14

Объект захватывается копией, если он неявно захвачен, а значение по умолчанию - = или если оно явно захвачено с захватом, который не включает &. Для каждого объекта, захваченного копией, в типе закрытия объявляется неназванный нестатический элемент данных.

В типе замыкания есть нестатический элемент данных (типа int).

/17

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

Нам даже не нужно это интерпретировать, поскольку этот параграф дает нам пример, очень похожий на OP:

void f(const int*);
void g() {
    const int N = 10;
    [=] {
        int arr[N]; // OK: not an odr-use, refers to automatic variable
        f(&N);      // OK: causes N to be captured; &N points to the
                    // corresponding member of the closure type
    };
}

Если мы применим это к OP примеру, мы увидим, что &i относится к внутреннему нестатическому элементу данных типа замыкания. Независимо от того, соответствует ли диагностическое сообщение, в стандарте не указано;)