/** * * \section COPYRIGHT * * Copyright 2013-2020 Software Radio Systems Limited * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the distribution. * */ #ifndef SRSLTE_MOVE_CALLBACK_H #define SRSLTE_MOVE_CALLBACK_H #include #include #include #include #include #include #include #if defined(__cpp_exceptions) && (1 == __cpp_exceptions) #define THROW_BAD_FUNCTION_CALL(const char* cause) throw std::bad_function_call{}; #else #define THROW_BAD_FUNCTION_CALL(cause) \ fprintf(stderr, "ERROR: exception thrown due to bad function call (cause: %s)\n", cause); \ std::abort() #endif namespace srslte { // NOTE: gcc 4.8.5 is missing std::max_align_t. Need to create a struct union max_alignment_t { char c; float f; uint32_t i; uint64_t i2; double d; long double d2; uint32_t* ptr; }; //! Size of the buffer used by "move_callback" to store functors without calling "new" constexpr size_t default_buffer_size = 32; template class move_callback; namespace task_details { //! Base vtable for move/call/destroy operations over the functor stored in "move_callback class oper_table_t { public: constexpr oper_table_t() = default; virtual R call(void* src, Args&&... args) const = 0; virtual void move(void* src, void* dest) const = 0; virtual void dtor(void* src) const = 0; virtual bool is_in_small_buffer() const = 0; }; //! specialization of move/call/destroy operations for when the "move_callback" is empty template class empty_table_t : public oper_table_t { public: constexpr empty_table_t() = default; R call(void* src, Args&&... args) const final { THROW_BAD_FUNCTION_CALL("function ptr is empty"); } void move(void* src, void* dest) const final {} void dtor(void* src) const final {} bool is_in_small_buffer() const final { return true; } }; //! specialization of move/call/destroy operations for when the functor is stored in "move_callback" buffer template class smallbuffer_table_t : public oper_table_t { public: constexpr smallbuffer_table_t() = default; R call(void* src, Args&&... args) const final { return (*static_cast(src))(std::forward(args)...); } void move(void* src, void* dest) const final { ::new (dest) FunT(std::move(*static_cast(src))); static_cast(src)->~FunT(); } void dtor(void* src) const final { static_cast(src)->~FunT(); } bool is_in_small_buffer() const final { return true; } }; //! move/call/destroy operations for when the functor is stored outside of "move_callback" buffer template class heap_table_t : public oper_table_t { public: constexpr heap_table_t() = default; R call(void* src, Args&&... args) const final { return (**static_cast(src))(std::forward(args)...); } void move(void* src, void* dest) const final { *static_cast(dest) = *static_cast(src); *static_cast(src) = nullptr; } void dtor(void* src) const final { delete (*static_cast(src)); } bool is_in_small_buffer() const final { return false; } }; //! Metafunction to check if a type is an instantiation of move_callback template struct is_move_callback : std::false_type {}; template struct is_move_callback > : std::true_type {}; //! metafunctions to enable different ctor implementations depending on whether the callback fits the small buffer template ::type> using enable_if_small_capture = typename std::enable_if::value, bool>::type; template ::type> using enable_if_big_capture = typename std::enable_if < Cap::value, bool>::type; } // namespace task_details template class move_callback { static constexpr size_t capacity = Capacity >= sizeof(void*) ? Capacity : sizeof(void*); ///< size of buffer using storage_t = typename std::aligned_storage::type; using oper_table_t = task_details::oper_table_t; static constexpr task_details::empty_table_t empty_table{}; public: move_callback() noexcept : oper_ptr(&empty_table) {} //! Called when T capture fits the move_callback buffer template = true> move_callback(T&& function) noexcept { using FunT = typename std::decay::type; static const task_details::smallbuffer_table_t small_oper_table{}; oper_ptr = &small_oper_table; ::new (&buffer) FunT(std::forward(function)); } //! Called when T capture does not fit the move_callback buffer template = true> move_callback(T&& function) { using FunT = typename std::decay::type; static const task_details::heap_table_t heap_oper_table{}; oper_ptr = &heap_oper_table; ptr = static_cast(new FunT{std::forward(function)}); } move_callback(move_callback&& other) noexcept : oper_ptr(other.oper_ptr) { other.oper_ptr = &empty_table; oper_ptr->move(&other.buffer, &buffer); } ~move_callback() { oper_ptr->dtor(&buffer); } move_callback& operator=(move_callback&& other) noexcept { oper_ptr->dtor(&buffer); oper_ptr = other.oper_ptr; other.oper_ptr = &empty_table; oper_ptr->move(&other.buffer, &buffer); return *this; } R operator()(Args&&... args) const noexcept { return oper_ptr->call(&buffer, std::forward(args)...); } bool is_empty() const { return oper_ptr == &empty_table; } bool is_in_small_buffer() const { return oper_ptr->is_in_small_buffer(); } private: union { mutable storage_t buffer; void* ptr; }; const oper_table_t* oper_ptr; }; template constexpr task_details::empty_table_t move_callback::empty_table; //! Generic move task using move_task_t = move_callback; } // namespace srslte #endif // SRSLTE_MOVE_CALLBACK_H