Захват лямбды в другой лямбде может нарушить квалификаторы const
Рассмотрим следующий код:
int x = 3;
auto f1 = [x]() mutable
{
return x++;
};
auto f2 = [f1]()
{
return f1();
};
Это не скомпилируется, потому что f1()
не является const, а f2
не объявлен как изменяемый. Означает ли это, что если у меня есть библиотечная функция, которая принимает произвольный аргумент функции и записывает его в лямбду, мне всегда нужно сделать эту лямбду изменчивой, потому что я не знаю, что могут передавать пользователи? Примечательно, что перенос f1
в std::function
кажется, решает эту проблему (как?).
Ответы
Ответ 1
Означает ли это, что если у меня есть библиотечная функция, которая принимает произвольный аргумент функции и записывает его в лямбду, мне всегда нужно сделать эту лямбду изменчивой, потому что я не знаю, что могут передавать пользователи?
Это дизайнерское решение для вашей библиотеки API. Вы можете требовать, чтобы клиентский код передавал функциональные объекты с помощью operator()
const
-qualified operator()
(который имеет место для mutable
лямбда-выражений non-). Если передается что-то другое, возникает ошибка компилятора. Но если контекст может потребовать аргумент объекта функции, который изменяет его состояние, тогда да, вы должны сделать внутреннюю лямбду mutable
.
Альтернативой может быть отправка возможности вызывать operator()
для экземпляра const
-qualified данного типа функции. Что-то в этом духе (обратите внимание, что это требует исправления для функциональных объектов как с const
и с operator()
non- const
operator()
, что приводит к неоднозначности):
template <class Fct>
auto wrap(Fct&& f) -> decltype(f(), void())
{
[fct = std::forward<Fct>(f)]() mutable { fct(); }();
}
template <class Fct>
auto wrap(Fct&& f) -> decltype(std::declval<const Fct&>()(), void())
{
[fct = std::forward<Fct>(f)]() { fct(); }();
}
Примечательно, что перенос f1 в std :: function, кажется, решает эту проблему (как?).
Это ошибка в std::function
из-за ее семантики стирания типов и копирования. Это позволяет вызывать operator()
non- const
-qualified operator()
, что можно проверить с помощью следующего фрагмента:
const std::function<void()> f = [i = 0]() mutable { ++i; };
f(); // Shouldn't be possible, but unfortunately, it is
Это известная проблема, по которой стоит ознакомиться с жалобой Titus Winter.
Ответ 2
Я начну с вашего второго вопроса. Тип std::function
стирает и содержит копию функтора, с которым он инициализирован. Это означает, что существует слой косвенности между std::function::operator()
и фактическим operator()
функтора operator()
.
Представьте, если хотите, удерживая что-то в вашем классе указателем. Затем вы можете вызвать операцию мутации в pointee из функции-члена const вашего класса, потому что она не влияет (в поверхностном представлении) на указатель, который содержит класс. Это похоже на то, что вы наблюдали.
Что касается вашего первого вопроса... "Всегда" - это слишком сильное слово. Это зависит от вашей цели.
-
Если вы хотите легко поддерживать мутирующие функторы, вам следует захватить изменчивую лямбду. Но будьте осторожны, это может повлиять на библиотечные функции, которые вы можете вызывать сейчас.
-
Если вы хотите отдавать предпочтение не мутантным операциям, то не изменяемая лямбда. Я говорю "одолжение", потому что, как мы заметили, систему типов можно "одурачить" с дополнительным уровнем косвенности. Таким образом, подход, который вы предпочитаете, будет проще в использовании, а не невозможным. Как гласит мудрый совет, сделайте правильное использование вашего API легким, а неправильное сложнее.