Каков возвращаемый тип выражения лямбда, если возвращается элемент вектора?

Рассмотрим следующий фрагмент:

#include <iostream>
#include <vector>
#include <functional>

int main() 
{
    std::vector<int>v = {0,1,2,3,4,5,6};
    std::function<const int&(int)> f = [&v](int i) { return v[i];}; 
    std::function<const int&(int)> g = [&v](int i) -> const int& { return v[i];};

    std::cout << f(3) << ' ' << g(3) << std::endl;
    return 0;
}

Я ожидал тот же результат: в f, v передается константной ссылкой, поэтому v[i] должен иметь тип const int&.

Однако я получаю результат

 0 3

Если я не использую std:: function, все в порядке:

#include <iostream>
#include <vector>
#include <functional>

int main() 
{
    std::vector<int>v = {0,1,2,3,4,5,6};
    auto f = [&v](int i) { return v[i];}; 
    auto g = [&v](int i) -> const int& { return v[i];};

    std::cout << f(3) << ' ' << g(3) << std::endl;
    return 0;
}

выход:

3 3

Таким образом, мне интересно:

  • Во втором фрагменте, каков тип возвращаемого выражения лямбда f? Является ли f тем же, что и g?

  • В первом фрагменте, что произошло при построении std::function f, вызвавшем ошибку?

Ответ 1

Возвращаемый тип lambda использует правила вывода типа auto, которые разделяют референциальность. (Первоначально он использовал несколько иной набор правил, основанный на преобразовании lvalue-to-rvalue (который также удалил ссылку), но это было изменено DR.)

Следовательно, [&v](int i) { return v[i];}; возвращает int. В результате в std::function<const int&(int)> f = [&v](int i) { return v[i];}; вызов f() возвращает ссылку на оборванную ссылку. Привязывание ссылки на временное продление срока службы временного, но в этом случае привязка произошла глубоко внутри механизма std::function, поэтому к моменту возврата f() временное уже ушло.

g(3) отлично, потому что возвращаемый const int & привязан непосредственно к векторному элементу v[i], поэтому ссылка никогда не висит.

Ответ 2

Что такое возвращаемый тип выражения лямбда, если возвращается элемент вектора?

Это неправильный вопрос.

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

Ответ дан в С++ 11 5.1.2 [expr.prim.lambda], пункт 5, где говорится, что если лямбда не имеет оператора return expr;, она возвращает void, в противном случае:

тип возвращаемого выражения после преобразования lvalue-to-rvalue (4.1), преобразование матрицы в указатель (4.2) и преобразования функции в указатель (4.3);

(см. комментарий T.C. ниже для DR 1048, который слегка изменил это правило, и компиляторы фактически реализуют измененное правило, но в этом случае это не имеет значения.)

преобразование lvalue-to-rvalue означает, что если оператор return возвращает lvalue как v[i], он распадается на rvalue, т.е. возвращает только int по значению.

Таким образом, проблема с вашим кодом заключается в том, что лямбда возвращает временный, но std::function<const int&(int)>, который его обертывает, связывает ссылку на это временное. Когда вы пытаетесь распечатать значение, которое временное уже ушло (оно привязано к объекту, который уже не существует), поэтому у вас есть поведение undefined.

Исправление состоит в том, чтобы использовать std::function<int(int)> или гарантировать, что лямбда возвращает действительную ссылку, а не rvalue.

В С++ 14 non-lambdas также может использовать вывод типа возврата, например:

auto f(std::vector<int>& v, int i) { return v[i]; }

Это соответствует аналогичным (но не идентичным) правилам правилам С++ 11 для lambdas, поэтому тип возврата int. Чтобы вернуть ссылку, вам необходимо использовать:

decltype(auto) f(std::vector<int>& v, int i) { return v[i]; }

Ответ 3

  • Я предполагаю, что первая лямбда может вернуться int или int& ¹. Не const int&, поскольку ссылка v не является объектом const (неважно, что сам захват не изменен, так как эта ссылка сама)

  • Время жизни временных объектов расширяется до конца охватывающей области при привязке к константе-ref


¹ Я ​​попытаюсь погрузиться в это позже. На данный момент я собираюсь с интуицией интуиции с регулярной дедукцией и скажу, что auto будет опускаться int, тогда как auto& будет выводить int&, поэтому я ожидаю, что фактический тип возврата int. Если кто-то бьет меня, тем лучше. У меня не будет времени на несколько часов.

См. тест, предоставленный @milleniumbug: Live On Coliru