Захват лямбды в другой лямбде может нарушить квалификаторы const - PullRequest
26 голосов
/ 24 апреля 2019

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

int x = 3;
auto f1 = [x]() mutable
{
    return x++;
};
auto f2 = [f1]()
{
    return f1();
};

Это не скомпилируется, поскольку f1() не является константой, а f2 не объявлено как изменяемое.Означает ли это, что если у меня есть библиотечная функция, которая принимает произвольный аргумент функции и записывает его в лямбду, мне всегда нужно сделать эту лямбду изменчивой, потому что я не знаю, что могут передавать пользователи?Примечательно, что завершение f1 в std::function, похоже, решает эту проблему (как?).

Ответы [ 2 ]

21 голосов
/ 24 апреля 2019

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

Это дизайнерское решение для API вашей библиотеки.Вы можете требовать, чтобы клиентский код передавал функциональные объекты с const -квалифицированным operator() (что имеет место для не mutable лямбда-выражений).Если передается что-то другое, возникает ошибка компилятора.Но если для контекста может потребоваться аргумент объекта функции, который изменяет его состояние, тогда да, вы должны сделать внутреннюю лямбду mutable.

. Альтернативой может быть отправка сообщения о способности вызывать operator() вconst -квалифицированный экземпляр данного типа функции.Что-то вроде этого (обратите внимание, что это требует исправления для функциональных объектов как с const, так и не 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 из-за ее семантики стирания типа и копирования.Он позволяет вызывать не const -квалифицированный operator(), что можно проверить с помощью следующего фрагмента:

const std::function<void()> f = [i = 0]() mutable { ++i; };

f(); // Shouldn't be possible, but unfortunately, it is

Это известная проблема, ее стоит проверить Жалоба Тита Винтера на этом.

6 голосов
/ 24 апреля 2019

Я начну с того, что сначала рассмотрю ваш второй вопрос. Тип std::function стирает и содержит копию функтора, с которым он инициализирован. Это означает, что существует слой косвенности между std::function::operator() и фактическим функтором operator().

Представьте, если хотите, удерживая что-то в вашем классе указателем. Затем вы можете вызвать операцию мутации в pointee из функции-члена const вашего класса, потому что она не влияет (в поверхностном представлении) на указатель, который содержит класс. Это похоже на то, что вы наблюдали.

Что касается вашего первого вопроса ... "Всегда" - это слишком сильное слово. Это зависит от вашей цели.

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

  2. Если вы хотите отдавать предпочтение немутантным операциям, то не изменяемая лямбда. Я говорю «одолжение», потому что, как мы заметили, систему типов можно «одурачить» с дополнительным уровнем косвенности. Таким образом, подход, который вы предпочитаете, будет проще в использовании, а не невозможным. Как гласит совет мудреца, сделать правильное использование вашего API простым, а неправильное сложнее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...