Почему при инициализации изменяемой лямбды не может быть изменяемых элементов данных?

Этот вопрос связан с этот предыдущий где было замечено, что инициализация захвата mutable лямбды несовместимы с диапазоном и итератором Boost transform для некоторых довольно малоизвестных и глубоко вложенных typedef ошибки, которые могут быть или не могут быть легко решены путем взлома источников Boost.Range.

Принятый ответ предложил хранить лямбда в std::function объект. Чтобы избежать потенциала virtual Затраты на вызов функции, я написал два функциональных объекта, которые могли бы стать потенциальными обходными путями. Они называются MutableLambda1 а также MutableLambda2 в коде ниже

#include <iostream>
#include <iterator>
#include <vector>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>

// this version is conforming to the Standard
// but is not compatible with boost::transformed
struct MutableLambda1
{
int delta;
template<class T> auto operator()(T elem) { return elem * delta++; }
};

// Instead, this version works with boost::transformed
// but is not conforming to the Standard
struct MutableLambda2
{
mutable int delta;
template<class T> auto operator()(T elem) const { return elem * delta++; }
};

// simple example of an algorithm that takes a range and laziy transformes that
// using a function object that stores and modifies internal state
template<class R, class F>
auto scale(R r, F f)
{
return r | boost::adaptors::transformed(f);
}

int main()
{
// real capturing mutable lambda, will not work with boost::transformed
auto lam = [delta = 1](auto elem) mutable { return elem * delta++; };
auto rng = std::vector<int>{ 1, 2, 3, 4 };

//boost::copy(scale(rng, lam), std::ostream_iterator<int>(std::cout, ","));                 /* ERROR */
//boost::copy(scale(rng, MutableLambda1{1}), std::ostream_iterator<int>(std::cout, ","));   /* ERROR */
boost::copy(scale(rng, MutableLambda2{1}), std::ostream_iterator<int>(std::cout, ","));     /* OK!   */
}

Живой пример это не скомпилирует строки с lam а также MutableLambda1и правильно печатает 1, 4, 9, 16 для линии с MutableLambda2,

Тем не менее проект стандарта упоминает

5.1.2 Лямбда-выражения [expr.prim.lambda]

5 […] Этот оператор вызова функции или шаблон оператора объявлен как const
(9.3.1) тогда и только тогда, когда лямбда-выражение
Параметр-объявление-предложение не сопровождается mutable, […]

11 Для каждого init-захвата нестатический элемент данных, названный
Идентификатор init-capture объявлен в типе замыкания. это
участник не поле биты и не mutable, […]

Это означает, что MutableLambda2 не является соответствующей заменой рукописной записи для инициализации mutable лямбда-выражение.

Вопросы

  • почему реализация init-захвата mutable лямбда, как он есть (т.е. оператор вызова неконстантной функции)?
  • почему, казалось бы, эквивалентная альтернатива mutable члены данных с const оператор вызова функции запрещен?
  • (бонус) почему Boost range и итератор transform полагаться на тот факт, что функция объектов operator() является const?

3

Решение

template<class L>
struct force_const_call_t {
mutable L f;
template<class...Args>
auto operator()(Args&&...args) const
{ return f(std::forward<Args>(args)...); }
};
template<class L>
force_const_call_t<L> force_const_call(L&&f){
return {std::forward<L>(f)};
}

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

2

Другие решения

Как отмечено в комментариях, изменяемая лямбда требует неконстантного оператора вызова функции, чтобы позволить постоянным ссылкам на функциональные объекты представлять чистые функции.

Оказывается, виновником моего приложения является Boost.Iterator, лежащий в основе реализации Boost.Range boost::adaptors::transformed, После некоторых копаний в документации Boost.Iterator требования к transform_iterator, получается что (жирный акцент мой)

Тип UnaryFunction должен быть назначаемым, копируемым, и
выражение f(*i) должен быть действительным где f является постоянным объектом типа
UnaryFunction, i является объектом типа Iterator, а где тип
f(*i) должно быть result_of<const
UnaryFunction(iterator_traits<Iterator>::reference)>::type
,

Поэтому объекты с непостоянной функцией не могут быть написаны с использованием лямбда-выражений, а должны быть написаны с использованием const вызов функции operator() и с mutable члены данных, представляющие государство. Это было также отмечено в это связано Q&.

Заметка: есть открыть отчет об ошибке за это.

1

По вопросам рекламы [email protected]