Как можно работать с std:: reference_wrapper в rbaue lambda?

В в этой статье говорится, что следующий код действителен С++ 11 и работает с GNU libstdС++:

int n;
std::vector<int> v;
...
std::function<bool(int)> f(std::cref([n](int i) {return i%n == 0));
std::count_if(v.begin(), v.end(), f);

Дело в том, что я полагал, что объект лямбда будет создан на сайте вызова, что сделает его временным объектом в этом фрагменте, поскольку он не хранится ни на одной переменной, а вместо этого ссылается на const создается и передается в std::function. Если это так, то лямбда-объект должен был быть уничтожен справа, оставив оборванную ссылку внутри f, что приведет к поведению undefined при использовании std::count_if.

Предполагая, что статья не ошибается, что не так в моей ментальной модели? Когда объект лямбда разрушен?

Ответ 1

ОК, давайте начнем с основ: вышеуказанный код, конечно, не является законным, потому что он плохо сформирован некоторыми довольно базовыми способами. Строка

std::function<bool(int)> f(std::cref([n](int i) {return i%n == 0));

при минимальном минимуме должно быть записано как

std::function<bool(int)> f(std::cref([n](int i) {return i%n == 0;}));

Обратите внимание, что код был написан в статье Dr.Dobb так, как это было в вопросе, т.е. любая формулировка законного кода уже довольно сомнительна.

После устранения простых синтаксических ошибок следующий вопрос заключается в том, может ли std::cref() использоваться для привязки к rvalue. Экстракция лямбда явно является временной в соответствии с пунктом 5.1.2 [expr.prim.lambda] (благодаря DyP для справки). Поскольку в целом было бы плохой идеей связать ссылку на временную и в других местах запрещено, std::cref() будет способом обойти это ограничение. Оказывается, что согласно 20.10 [function.objects] абзац 2 std::cref() объявлен как

template <class T> reference_wrapper<const T> cref(const T&) noexcept;
template <class T> void cref(const T&&) = delete;
template <class T> reference_wrapper<const T> cref(reference_wrapper<T>) noexcept;

То есть оператор неверен даже после исправления ошибок синтаксиса. Ни gcc, ни clang компилировать этот код ( Я использовал довольно недавние версии обоих компиляторов с их соответствующими стандартными библиотеками С++). То есть, основываясь на вышеуказанной декларации, этот код явно незаконен!

Наконец, нет ничего, что могло бы продлить время жизни временного в вышеупомянутом выражении. Единственная причина, по которой продлевается срок жизни, - это когда он или один из его членов данных немедленно привязан к ссылке [ const]. Обтекание вызова функции вокруг временного запрещает это продление срока службы.

Вкратце: код, цитируемый в статье, не является законным на разных уровнях!

Ответ 2

Я автор вышеупомянутой статьи и извиняюсь за свою ошибку. В этом случае больше никто не виноват. Я просто попрошу редактора добавить ошибки:

1) Замените

std::count_if(v.begin(), v.end(), std::cref(is_multiple_of(n)));

с

is_multiple_of f(n);
std::count_if(v.begin(), v.end(), std::cref(f));

2) Замените

std::count_if(v.begin(), v.end(), std::cref([n](int i){return i%n == 0;}));

с

auto f([n](int i){return i%n == 0;});
std::count_if(v.begin(), v.end(), std::cref(f));

3) Замените

std::function<bool(int)> f(std::cref([n](int i) {return i%n == 0));

с

auto f1([n](int i){return i%n == 0;});
std::function<bool(int)> f(std::cref(f1));

Во всех случаях проблема такая же (как хорошо объяснил Дитмар Кюль, +1 к нему): мы называем std::cref временным. Эта функция возвращает std::reference_wrapper сохранение указателя на временное, и этот указатель будет болтаться, если std::reference_wrapper переживает временный. В основном это то, что происходит в случае 3 выше (в котором также содержится опечатка).

В случаях 1 и 2, std::reference_wrapper не пережил бы временный. Однако, поскольку перегрузки std::cref, принимающие временные (rvalues), удаляются, код не должен компилироваться (включая случай 3). На момент публикации эти версии не соответствовали стандарту, как сегодня. Код, используемый для компиляции, но он не используется при использовании новых версий стандартной библиотеки. Однако это не оправдание моей ошибки.

В любом случае, я считаю, что основной смысл статьи, то есть использование std::reference_wrapper, std::cref и std::ref, чтобы избежать дорогостоящих копий и динамических распределений, остается в силе, конечно, при условии, что время жизни упомянутого объекта достаточно длинное.

Опять же, я приношу свои извинения за неудобства.

Обновление: Статья исправлена. Спасибо uk4321, DyP и, особенно, lvella и Dietmar Kühl за поднятие и обсуждение проблемы.