Лямбда захватывает ссылку rvalue по ссылке

Является ли приведенный ниже код стандартным? (богболт)

т.е. by-ref захватывает ссылку пересылки, представляющую временную, и возвращает результирующее лямбда-значение из функции в том же выражении.

Конечно, сохранение лямбды для последующего использования приведет к тому, что она будет содержать висячую ссылку, но я имею в виду точное использование внутри main.

Сомнения, которые у меня возникают, связаны с этим ответом SO и потенциально это языковой дефект. В частности, есть один пугающий комментарий, в котором говорится: "правило срока действия захвата ссылок в стандартных ссылках относится к захваченным переменным, а не к данным и их области действия". мой код.

#include <stdlib.h>
#include <string.h>
#include <cassert>

template<typename F>
auto invoke(F&& f)
{
    return f();
}

template<typename F>
auto wrap(F&& f)
{
    return [&f]() {return f();}; // <- this by-ref capture here
}

int main()
{
    int t = invoke(wrap(
        []() {return 17;}
    ));

    assert(t == 17);
    return t;
}

person haelix    schedule 07.04.2019    source источник
comment
Это ваш реальный вариант использования? Потому что вы могли бы просто сделать return f();, конечно.   -  person Hatted Rooster    schedule 07.04.2019
comment
просто проверка. Вы понимаете, что эти универсальные ссылки в invoke и wrap не распространяются, верно? Вы также можете передавать ссылки lvalue.   -  person Richard Hodges    schedule 07.04.2019
comment
@SombreroChicken в реальном случае использования wrap, конечно, что-то делает   -  person haelix    schedule 07.04.2019
comment
@RichardHodges да, я мог бы объявить invoke для получения ссылки на l-значение, но это не изменит вопроса - функция invoke просто служит способом использования лямбда-выражения, возвращаемого из wrap, внутри того же выражения   -  person haelix    schedule 07.04.2019


Ответы (2)


В вашем коде был UB для относительно короткого окна. (Примечание: это очень странная вещь). Исходные правила захвата лямбда по ссылке гласили, что ссылка действительна только до тех пор, пока захваченная переменная не выйдет за пределы области действия.

Это может привести к своего рода захвату по ссылке, что в противном случае невозможно в стандарте C++. (Самое близкое, что вы могли бы получить, это ссылка на одночленную структуру, содержащую ссылку)

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

Поскольку большинство (все?) ABI реализуют ссылочные аргументы как указатели под капотом, это привело бы к ссылочным аргументам на аргументы функций, которые являются ссылками, висячими после возврата лямбды.

Ни один компилятор не использовал этот факт. Эта оптимизация никогда не использовалась, она просто рассматривалась как возможная. Захват ссылки лямбды имеет время жизни правила ссылки на переменную, которое никогда не использовалось ни одним компилятором (или, по крайней мере, ни одним, о котором я слышал).

Когда это было обнаружено, это было разрешено как устранение дефекта в стандарте, что означает, что он задним числом переопределил, что c++11 означает.

Таким образом, хотя под историческим c++11 компилятор, технически это был UB, в настоящее время не соответствует c++11 может обрабатывать его как UB, и все исторические компиляторы C++11 обрабатывают его так же, как и современные компиляторы. Так что вы в безопасности.

person Yakk - Adam Nevraumont    schedule 07.04.2019
comment
Блестящий ответ с инклюзивным уроком истории. Легендарный. - person Richard Hodges; 07.04.2019
comment
Я очень смущен этим ответом, последние 14 лет я программировал почти исключительно на C ++ в профессиональной среде, большая часть из которых связана с финансами / малой задержкой и т. Д. Я считаю, что у меня достаточно информации, чтобы пометить это как ответ, но я постараюсь подтянуть тему. - person haelix; 08.04.2019

Да, в вашем коде нет УБ. f привязан к лямбде, но вы вызываете лямбду, которая фиксирует f в том же выражении, поэтому ее время жизни не закончилось. В связанном отчете о дефекте поясняется, что означает захват ссылки по ссылке. Проблема была решена путем разъяснения того, что захват ссылки на самом деле является ссылкой на объект, к которому привязана захваченная ссылка.

В вашем случае захваченное f является ссылкой на лямбду (а не на параметр f).

person Rakete1111    schedule 07.04.2019