Search code examples
c++sdl

Pass const std::function<void()> as parameter in SDL_AddTimer as void*


I am trying to pass a const std::function<void()> & as void * to SDL_AddTimer, which its 3rd parameter is void *.

I tried with reinterpret_cast

SDL_AddTimer(interval, wrapper, reinterpret_cast<void *>(const_cast<std::function<void()> *>(fn)));

But on my wrapper, it crashes

uint32_t wrapper(uint32_t interval, void *param) {
  auto fn = static_cast<std::function<void()> *>(param);
  (*fn)(); // crashes here.
  return interval;
}

What I am doing wrong?

EDIT: Usage:

timer t;
t.set(1000, []() {
  std::cout << "Hello, world!" << std::endl;
});

Solution

  • You can't cast a std::function into a void* like you are doing (you can't even cast a reference into a pointer the way you are doing).

    But, you can pass the address of the std::function object itself. That is what your wrapper is expecting anyway, eg:

    auto &fn_ref = const_cast<std::function<void()> &>(fn); // note & not *
    SDL_AddTimer(interval, wrapper, &fn_ref);
    
    uint32_t wrapper(uint32_t interval, void *param) {
      auto fn = static_cast<std::function<void()> *>(param);
      (*fn)();
      return interval;
    }
    

    Just make sure the std::function object stays alive while the SDL timer is running. However, based on your usage example, it appears the std::function object may go out of scope before the timer ever has a chance to call it. So, you will have to save the std::function object somewhere until the timer stops running, eg:

    class timer {
        SDL_TimerID timerID = 0;
        std::function<void()> timerFunc;
    
    public:
        timer() = default;
        ~timer();
    
        void set(uint32_t interval, const std::function<void()> &fn);
        void stop();
    };
    
    uint32_t wrapper(uint32_t interval, void *param) {
      auto fn = static_cast<std::function<void()> *>(param);
      (*fn)();
      return interval;
    }
    
    timer::~timer() {
        stop();
    }
    
    void timer::set(uint32_t interval, const std::function<void()> &fn) {
        stop();
        timerFunc = fn;
        timerID = SDL_AddTimer(interval, wrapper, &timerFunc);
    }
    
    void timer::stop() {
        if (timerID != 0) {
            SDL_RemoveTimer(timerID);
            timerID = 0;
        }
    }