Передать лямбду с захватом контекста по указателю

Тема в разделе "LANGS.C", создана пользователем HoShiMin, 30 июн 2019.

  1. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Все мы знаем, что лямбду можно передать, как указатель на функцию, только без захвата контекста:
    Код (C++):
    1.  
    2. int func(int (*callback)(int arg), int arg)
    3. {
    4.     return callback(arg);
    5. }
    6.  
    7. int main()
    8. {
    9.     int result = func([](int arg) -> int {
    10.         return arg * 2;
    11.     }, 10);
    12. }
    13.  
    Однако, используя std::bind, можно сделать нечто подобное:
    Код (C++):
    1.  
    2. typedef std::function<int(int, float)> func_t;
    3.  
    4. void func(void* arg)
    5. {
    6.     auto pbind = (std::_Binder<int, func_t, int, float>*)arg;
    7.     (*pbind)();
    8. }
    9.  
    10. int main()
    11. {
    12.     func_t f = [&](int a, float b) -> int { return a * 2; };
    13.     auto bind = std::bind(f, 1, 2.0f);
    14.     func(&bind);
    15. }
    16.  
    Осталось это шаблонизировать, чтобы работать с произвольным числом аргументов произвольных типов.

    Как составить шаблон, чтобы построить std::_Binder на основе std::function без ограничений на количество и типы аргументов? А именно - вывести тип переданного _Binder'a. Кроме того, std::bind не проверяет типы аргументов (например, вместо int можно передать строку) - как гарантировать, что все переданные аргументы нужных типов?

    Конечная хотелка - сделать возможность передавать лямбды с захватом контекста в функции, принимающие только сырые указатели на каллбэки. Например, хочу написать стаб, позволяющий передавать в CreateThread лямбды с контекстом: некую прослойку, которая вытащит из переданного указателя std::bind и вызовет лямбду - по аналогии, как сделано в std::thread.
    --- Сообщение объединено, 30 июн 2019 ---
    По аналогии с std::thread сделал такое, фактически копипастом:
    Код (C++):
    1.  
    2. class ThreadWrapper {
    3. public:
    4.     template <class Tuple, size_t... indices>
    5.     static void __stdcall stub(void* arg) {
    6.         const std::unique_ptr<Tuple> fn_vals(static_cast<Tuple*>(arg));
    7.         Tuple& tuple = *fn_vals;
    8.         std::invoke(std::move(std::get<indices>(tuple))...);
    9.     }
    10.  
    11.     template <class Tuple, size_t... indices>
    12.     static constexpr auto get_invoke(std::index_sequence<indices...>)
    13.     {
    14.         return &stub<Tuple, indices...>;
    15.     }
    16.  
    17.     template <class Func, class... Args>
    18.     static void call(Func&& func, Args&&... args)
    19.     {
    20.         using FuncTuple = std::tuple<std::decay_t<Func>, std::decay_t<Args>...>;
    21.         auto decay_copied = std::make_unique<FuncTuple>(std::forward<Func>(func), std::forward<Args>(args)...);
    22.         constexpr auto invoker = get_invoke<FuncTuple>(std::make_index_sequence<1 + sizeof...(Args)>{});
    23.         invoker(decay_copied.get());
    24.         decay_copied.release();
    25.     }
    26. };
    27.  
    28. int main()
    29. {
    30.     int c = 10;
    31.     ThreadWrapper::call([&](int a, float b) -> int {
    32.         int d = a * 2;
    33.         printf("c = %i\r\n", c);
    34.         return d;
    35.     }, 123, 0.02f);
    36. }
    37.  
    И это даже работает. Но совершенно не понимаю, как. Знатоки шаблонной магии, объясните, что здесь происходит и почему?
     
  2. CurryHowardIsomorphism

    CurryHowardIsomorphism Member

    Публикаций:
    0
    Регистрация:
    13 май 2017
    Сообщения:
    97
    Это вообще не проблема, т.к. CreateThread принимает кроме начального адреса ещё указатель на аргумент для вызываемой функции.
    Проблема — это когда принимается только указатель на функцию.
     
  3. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Да, уже сделал по примеру выше. Но всё равно, шаблонная магия выглядит страшно...
    Этого, к счастью, не требуется
     
  4. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.323
    проблема в том, что ты пытаешься прикрутить плюсы к сишечным апи, которые ничего и знать не знают о захвате контекста и лямбдах... зачем тебе использовать CreateThread в плюсах, когда есть готовый std::thread? при этом std::thread чисто теоретически может инициализировать для потока какие-либо глобальный переменные/данные/конструкторы, которые необходимы для корректной работы плюсового рантайма, как некоторые писали о _beginthread и _beginthreadex...
     
  5. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Для единообразия и чтобы понять, как вообще они сумели сделать передачу контекста по сырым указателям: пишу обёртки над потоками, чтобы собрать в одном классе все специфичные для Win32-потоков вещи - контексты, заморозку, APC, и сделать это, в первую очередь, удобным. А удобство, в том числе, и в поддержке полноценных лямбд. Пока полёт нормальный.

    А что про них писали? Что там может вылезти, если юзать CreateThread в плюсовом окружении? Всегда юзал CreateThread и ни разу не встречал каких-то проблем.
     
  6. Rel

    Rel Well-Known Member

    Публикаций:
    2
    Регистрация:
    11 дек 2008
    Сообщения:
    5.323
    инициализация всяческих errno, thread local storage и тд...
     
  7. sn0w

    sn0w Active Member

    Публикаций:
    0
    Регистрация:
    27 фев 2010
    Сообщения:
    958
    чес сказать - то тут самое адекватное - не толкать указатели в стек или регистры, а просто использовать положительные оффсеты rbp/rsp/ebp/esp