Странное поведение undefined с сложным выражением лямбда
Я боролся с проблемой с лямбда-выражениями, которая ставила под угрозу мой проект. Я нашел решение, но я хотел бы точно понять, как и почему он работает, и если он надежный.
#include <iostream>
#include <functional>
#include <unordered_map>
typedef std::function<const int&(const int&)> Callback;
int f(int i, Callback callback) {
if (i <= 2) return 1;
return callback(i-1) + callback(i-2);
}
int main(void) {
std::unordered_map<int, int> values;
Callback callback = [&](const int& i) {
if (values.find(i) == values.end()) {
int v = f(i, callback);
values.emplace(i, v);
}
return values.at(i);
};
std::cout << f(20, callback) << std::endl;
return 0;
}
Я знаю, что это сумасшедший способ вычислить 20-й номер Фибоначчи, но это самый компактный SSCCE, который я смог разработать.
Если я скомпилирую код выше с помощью g++ -O0
, и я запускаю программу, я получаю 6765
, что на самом деле является 20-м числом Фибоначчи. Если я скомпилирован с -O1
, -O2
или -O3
, я получаю 262144
, что является мусором.
Если я профилирую программу с помощью Valgrind (компиляция с -O0 -g
), я получаю Conditional jump or move depends on uninitialised value(s)
в строке std::cout << f(20, callback) << std::endl;
, но трассировка стека не говорит ничего полезного.
Я не знаю, почему я закончил с этим:
Callback callback = [&](const int& i) -> const int& {
С помощью этой небольшой модификации все работает как ожидается, компилируется с любым уровнем оптимизации, и Valgrind не сообщает о проблемах.
Можете ли вы помочь мне понять, что происходит?
Ответы
Ответ 1
Без -> const int&
возвращаемый тип лямбда int
. Так как тип возврата Callback
равен const int&
, Callback
заканчивается возвратом ссылки на временное значение, которое оно использует для хранения возвращаемого значения выражения лямбда. Undefined.
Для этого простого примера простое исправление заключается в том, чтобы вообще не использовать ссылки (Пример в Coliru), но я полагаю, что ваш реальный код обрабатывает более тяжеловесные объекты, чем int
. Прискорбно, что std::function
не указывается для предупреждения, когда вы назначаете функцию, которая возвращает ссылку без ссылки на std::function
, тип возврата которой является ссылкой.
Ответ 2
Casey ответ. Я просто хотел бы что-то добавить. (Мне кажется, что добавление поверх Кейси хороших ответов становится обычным делом моего;-).) Собственно, мое замечание на gd1 комментарий Casey .
Я думаю, что OP использует g++ -std=c++1y
, потому что в С++ 11 возвращаемый тип показанного там лямбда установлен в void
. Действительно, компилятор выводит возвращаемый тип только в том случае, если тело лямбда содержит единственный оператор return
. В противном случае компилятор предполагает, что этот тип void
. В С++ 14 более эффективный автоматический вывод для возвращаемого типа (не только для лямбда, но и для функций).
Как можно видеть n3582 - документ, который был одобрен для С++ 14 и для которого реализация gcc
ссылка - указывает, что
plain auto никогда не выводит ссылку, [...]
В примере OP возвращаемый тип int
(не const int&
). Я считаю, что можно использовать -> decltype(auto)
для автоматического вывода, дающего ссылку.