Мне даже интересно, а как это связано?
так, что реализация всегда будет видна (ну ладно, не "всегда" а почти всегда) и использовать void *, что прячется внутри std::function, не нужно будет. Можно будет писать template<typename F> void g(F && f) { ... f(); } вместо void g(std::function<void ()> f); + реализация где-то в другом месте.
Ну std::function еще нужен если вдруг тебе понадобится хранить функтор где-то со стиранием типов
так есть std::variant, если всё на виду
Эээ, а вариант тут причем? :)
вот вы мыслите в старой парадигме. В std::variant можно перечислить все типы (функциональных объектов и указателей на функцию). Они ж все видны
Да, и как это связано с модулями и функторами?
Классический пример где используется std::funtion - callback. Его не заменить.
Его как раз можно заменить на шаблонный аргумент. Если вынести реализацию функции в хедер.
я не про границы м/у so и dll — это как раз законный маргинальный случай, где std::function и прочие void * вполне себе законны
а как в нём лямбду перечислить, простите?
Да даже в своём коде - далеко не всегда хочется ломать границы модулей, только чтобы перечислить все возможные типы функторов
Ну, в принципе по идее можно указать ее через decltype, но зачем?
Ну нет же. Колбеки обычно требуют type erasure, потому что все обработчики кладутся в очередь или что-нибудь в этом роде.
Колбеки бывают очень разные
грубо говоря вот так auto f() { auto l = [] {}; return std::variant<decltype(l)>{l}; }
Можно действовать от закрытого мира, тогда TypeErasure равносилен перечислению всех возможностей (их конечное число)
В случае очереди предполагается хранение, и вот тут уже без стирания типов не обойтись
не, мне надо f.set_callback([this] () { blah; blah; })
Да, пожалуй я слишком обобщил. Но грубо говоря boost.asio требует std::function или чего-то похожего.
если реализация колбэка видна, то он просто шаблонный
как в том примере, с которого начал вопрос
button::on_enable_change
я сейчас не про границы между модулями и, условно говоря, *.o-файлами
а без них там variant нет
А если его надо хранить в некой очереди как в примере с boost.asio выше?
std::queue<std::variant<F1, F2, F3>>
> можно действовать от закрытого мира, тогда TypeErasure равносилен перечислению всех возможностей (их конечное число) < но не нужно
Тогда эта очередь сможет хранить только три типа колбеков. Зачем они тогда вообще нужны? Может заменить на enum class Action { F1, F2, F3 }?
повторяю. В собственном коде есть конечное число типов и все они видимые. На границах с чужим кодом void * понадобится. Я не спорю с этим.
Как должна выглядеть простейшая реализация однопоточного экзекютера через вариант? class executor { std::queue<std::function<void()> m_queue; public: void spawn(std::funtion<void()> f) { m_queue.push(f); } void run() { while (!m_queue.empty()) { m_queue.front()(); m_queue.pop(); } } };
для начала замените std::function<void()> на шаблонный параметр :)
Я сказал простейшего. =)
не вижу усложнений от автозамены и парочки корректур)
Дело не в границах кода, дело в том, что ваша очередь поддерживает всего три типа функторов
Ну добавьте четвертый в список
Эм, ну, ээ, ну в принципе можно.
В общем случае от variant либо type erasure не избавиться...
Ну Анатолий верно замечает, что для закрытой программы variant и TypeErasure в позиции шаблонного параметра просто одинаковые
Это замечательно с теоретической точки зрения. Но на практике как мою задачу решать?
Задачи пока не вижу. Есть какое-то решение задачи, представленное каким-то кодом
Задача написать простейший однопоточный экзекютор.
Это не задача, это решение
Обсуждают сегодня