Замена std :: function изнутри самой себя (путем перемещения-присваивания * this?) - PullRequest
0 голосов
/ 28 мая 2018

Можно ли заменить один std::function изнутри на себя другим std::function?

Следующий код не компилируется:

#include <iostream>
#include <functional>

int main()
{
    std::function<void()> func = []()
    {
        std::cout << "a\n";
        *this = std::move([]() { std::cout << "b\n"; });
    };
    func();
    func();
    func();
}

Можно ли изменить его для компиляции?
Сообщение об ошибке прямо сейчас: 'this' не было захвачено для этой лямбда-функции - что я полностью понимаю.Я не знаю, однако, как я мог поймать func this -поинтер.Я думаю, это даже не std::function внутри лямбды, пока ?!Как это можно сделать?

Справочная информация: Я хочу добиться следующего: при первом вызове данного std::function я хотел бы выполнить некоторую работу по инициализации, а затем заменитьоригинальная функция с оптимизированной.Я хочу добиться этого прозрачно для пользователя моей функции.

Ожидаемый результат приведенного выше примера:

a
b
b

Ответы [ 2 ]

0 голосов
/ 28 мая 2018

Это технически решает вашу проблему:

std::function<void()> func = [&func]{
  std::cout << "a\n";
  func = []{ std::cout << "b\n"; };
};

Но это не очень хороший план.Время жизни и поведение функции std связано с локальной переменной стека.Копии не в состоянии выполнить то, что вы, вероятно, хотите почти в любом смысле - они продолжают печатать "a\b", если они не пересекаются с ошибкой, потому что оригинал func выпал из области видимости.


Чтобы исправить это, мы должныразвернуть несколько больших орудий;для начала Королева функционального программирования г-жа Y Combinator:

std::function<void()> func = y_combinate( [](auto&& self){
  std::cout << "a\n";
  self = []{ std::cout << "b\"; };
} );

Y Combinator принимает функцию сигнатуры F = (F,Args...)->R, а затем возвращает функцию сигнатуры (Args...)->R.Вот как языки без состояний управляют рекурсией, когда вы не можете назвать себя, пока не получили имя.

Написание Y-комбинатора проще, чем вы опасаетесь в C ++:

template<class F>
struct y_combinator_t {
  F f;
  template<class...Args>
  auto operator()(Args&&...args)
  -> typename std::result_of< F&( F&, Args&&... ) >::type
  {
    return f( f, std::forward<Args>(args)... );
  }
};
template<class F>
y_combinator_t<typename std::decay<F>::type> y_combinate( F&& f ) {
  return {std::forward<F>(f)};
}

к сожалению, это не такне работает, поскольку тип self, передаваемый лямбде, на самом деле является типом оригинальной лямбды.И лямбда с b-печатью - это не связанный тип.Поэтому, когда вы пытаетесь self = []{ std::cout << "b\n"; }, вы получаете ошибку, встроенную в некоторые относительно глубокие ошибки спама в шаблонах.

Sad;но только временная задержка.

Нам нужен тип, который очень трудно назвать - F = std::function<void(F)> - std::function, который принимает в качестве одного аргумента экземпляр объекта того же типа.

Обычно это невозможно сделать, но с небольшим количеством шаблонного дурачества ... Здесь я сделал это раньше.

Тогда ваш код читает:

std::function<void()> func = y_combinate( recursive_func< void(own_type&) >([](auto&& self){
  std::cout << "a\n";
  self = [](auto&&){ std::cout << "b\n"; };
}) );

и при вызове данной копии func сначала печатается "a\n", затем при каждом последующем вызове печатается "b\n".Копии b-принтеров также печатают b, но копии a-принтеров будут печататься в первый раз перед переходом.

Живой пример .

self в этомкод recursive_func< void(own_type&) >, поэтому вы можете сделать то же самое в b-принтере, как и в a-принтере.

0 голосов
/ 28 мая 2018

Вы не можете использовать this внутри лямбды для обозначения лямбды.this будет относиться только к классу включения, которого в вашем случае нет, поэтому вы не можете использовать this.Однако вы можете захватить func и переназначить это:

std::function<void()> func = [&func]()
{
    std::cout << "a\n";
    func = []() { std::cout << "b\n"; }; // note the missing move, a lambda
                                         // is already an rvalue
};

Обратите внимание, однако, что если вы позволите func пережить его область действия (скажем, возвращая его из функции по значению), не вызывая его сначала(фактически переназначая объект сохраненной функции), тогда вы получите висячую ссылку.

Я думаю, это даже не std::function внутри лямбды, пока?!

Это действительно так.Имя входит в область действия сразу после своего объявления, поэтому прямо перед введением =, func типа std::function<void()>.Так что в тот момент, когда вы вводите лямбду, вы уже можете захватить func.

...