9
\$\begingroup\$

Some time ago I implemented dynamic_array and posted it on Code Review. It used std::function internally for type erasure. This inspired me to implement a standard-conforming std::function myself. This is just a showcase of how static polymorphism with templates can play well with dynamic polymorphism with inheritance and virtual functions.

Incidentally, some time ago I implemented std::invoke and my own invoke_r and also posted it on Code Review. Internally, function uses invoke_r. Its implementation is very short, so I included it as part of the code.

Here is the function.hpp header: (the deduction guide was really painful to write)

// C++17 std::function implementation
// [func.wrap] and [depr.func.adaptor.typedefs]
#ifndef INC_FUNCTION_HPP_QLxVtsa5Mv
#define INC_FUNCTION_HPP_QLxVtsa5Mv
#include <cstddef>
#include <exception>
#include <functional>
#include <memory>
#include <typeinfo>
#include <type_traits>
namespace my_std {
 // [func.wrap.badcall], class bad_function_call
 struct bad_function_call :std::exception {
 // [func.wrap.badcall.const], constructor
 bad_function_call() = default;
 };
 // [func.wrap.func], class template function
 template <class F>
 class function; // not defined
 namespace detail {
 // [depr.func.adaptor.typedefs], typedefs to support function binders
 template <class... Args>
 struct function_base {};
 template <class T>
 struct function_base<T> {
 using argument_type [[deprecated]] = T;
 };
 template <class T, class U>
 struct function_base<T, U> {
 using first_argument_type [[deprecated]] = T;
 using second_argument_type [[deprecated]] = U;
 };
 // trait to check if a type is a pointer to function
 template <class T>
 struct is_function_pointer :std::false_type {};
 template <class T>
 struct is_function_pointer<T*> :std::is_function<T> {};
 template <class T>
 inline constexpr bool is_function_pointer_v = is_function_pointer<T>::value;
 // trait to check if a type is a specialization of the class template function
 template <class T>
 struct is_function :std::false_type {};
 template <class T>
 struct is_function<function<T>> :std::true_type {};
 template <class T>
 inline constexpr bool is_function_v = is_function<T>::value;
 // INVOKE<R>(f, args...), see [func.require]/2
 template <class R, class F, class... Args>
 R invoke_r(F&& f, Args&&... args)
 noexcept(std::is_nothrow_invocable_r_v<R, F, Args...>)
 {
 if constexpr (std::is_void_v<R>)
 return static_cast<void>(
 std::invoke(std::forward<F>(f), std::forward<Args>(args)...)
 );
 else
 return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
 }
 // polymorphic base for callable wrappers
 template <class R, class... Args>
 struct callable_base {
 virtual ~callable_base() {}
 virtual R invoke(Args...) const = 0;
 virtual std::unique_ptr<callable_base> clone() const = 0;
 virtual const std::type_info& target_type() const noexcept = 0;
 virtual void* target() const noexcept = 0;
 };
 // callable wrapper
 template <class F, class R, class... Args>
 struct callable :callable_base<R, Args...> {
 using Base = callable_base<R, Args...>;
 public:
 callable(F f)
 :func{std::move(f)}
 {
 }
 ~callable() {}
 R invoke(Args... args) const override
 {
 return invoke_r<R>(func, std::forward<Args>(args)...);
 }
 std::unique_ptr<Base> clone() const override
 {
 return std::unique_ptr<Base>{new callable{func}};
 }
 const std::type_info& target_type() const noexcept override
 {
 return typeid(F);
 }
 void* target() const noexcept override
 {
 return &func;
 }
 private:
 mutable F func;
 };
 }
 // [func.wrap.func], class template function
 template <class R, class... Args>
 class function<R(Args...)> :public detail::function_base<Args...> {
 public:
 using result_type = R;
 // [func.wrap.func.con], construct/copy/destroy
 function() noexcept
 {
 }
 function(std::nullptr_t) noexcept
 {
 }
 function(const function& f)
 :func{f ? f.func->clone() : nullptr}
 {
 }
 function(function&& f) noexcept // strengthened
 {
 swap(f);
 }
 template <class F,
 std::enable_if_t<std::is_invocable_r_v<R, F&, Args...>, int> = 0>
 function(F f)
 {
 if constexpr (detail::is_function_pointer_v<F> ||
 std::is_member_pointer_v<F> ||
 detail::is_function_v<F>) {
 if (!f) return;
 }
 func.reset(new detail::callable<F, R, Args...>{std::move(f)});
 }
 function& operator=(const function& f)
 {
 function{f}.swap(*this);
 return *this;
 }
 function& operator=(function&& f)
 {
 swap(f);
 return *this;
 }
 function& operator=(std::nullptr_t) noexcept
 {
 func.reset();
 return *this;
 }
 template <class F,
 std::enable_if_t<
 std::is_invocable_r_v<R, std::decay_t<F>&, Args...>,
 int> = 0>
 function& operator=(F&& f)
 {
 function{std::forward<F>(f)}.swap(*this);
 return *this;
 }
 template <class F>
 function& operator=(std::reference_wrapper<F> f) noexcept
 {
 function{f}.swap(*this);
 return *this;
 }
 ~function() = default;
 // [func.wrap.func.mod], function modifiers
 void swap(function& other) noexcept
 {
 using std::swap;
 swap(func, other.func);
 }
 // [func.wrap.func.cap], function capacity
 explicit operator bool() const noexcept
 {
 return static_cast<bool>(func);
 }
 // [func.wrap.func.inv], function invocation
 R operator()(Args... args) const
 {
 if (*this)
 return func->invoke(std::forward<Args>(args)...);
 else
 throw bad_function_call{};
 }
 // [func.wrap.func.targ], function target access
 const type_info& target_type() const noexcept
 {
 if (*this)
 return func->target_type();
 else
 return typeid(void);
 }
 template <class T>
 T* target() noexcept
 {
 if (target_type() == typeid(T))
 return reinterpret_cast<T*>(func->target());
 else
 return nullptr;
 }
 template <class T>
 const T* target() const noexcept
 {
 if (target_type() == typeid(T))
 return reinterpret_cast<const T*>(func->target());
 else
 return nullptr;
 }
 private:
 std::unique_ptr<detail::callable_base<R, Args...>> func = nullptr;
 };
 namespace detail {
 template <typename T>
 struct deduce_function {};
 template <typename T>
 using deduce_function_t = typename deduce_function<T>::type;
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...)> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) const> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) volatile> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) const volatile> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) &> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) const &> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) volatile &> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) const volatile &> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) noexcept> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) const noexcept> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) volatile noexcept> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) const volatile noexcept> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) & noexcept> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) const & noexcept> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) volatile & noexcept> {
 using type = R(Args...);
 };
 template <typename G, typename R, typename... Args>
 struct deduce_function<R (G::*)(Args...) const volatile & noexcept> {
 using type = R(Args...);
 };
 }
 template <class R, class... Args>
 function(R (*)(Args...)) -> function<R(Args...)>;
 template <class F, class Op = decltype(&F::operator())>
 function(F) -> function<detail::deduce_function_t<Op>>;
 // [func.wrap.func.nullptr], null pointer comparisons
 template <class R, class... Args>
 bool operator==(const function<R(Args...)>& f, std::nullptr_t) noexcept
 {
 return !f;
 }
 template <class R, class... Args>
 bool operator==(std::nullptr_t, const function<R(Args...)>& f) noexcept
 {
 return !f;
 }
 template <class R, class... Args>
 bool operator!=(const function<R(Args...)>& f, std::nullptr_t) noexcept
 {
 return static_cast<bool>(f);
 }
 template <class R, class... Args>
 bool operator!=(std::nullptr_t, const function<R(Args...)>& f) noexcept
 {
 return static_cast<bool>(f);
 }
 // [func.wrap.func.alg], specialized algorithms
 template <class R, class... Args>
 void swap(function<R(Args...)>& lhs, function<R(Args...)>& rhs) noexcept
 {
 lhs.swap(rhs);
 }
}
#endif
asked Jun 8, 2019 at 11:43
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Nice, clean and functional.
Still, there are a few things:

  1. If you don't have to declare a special member-function, just don't:
    bad_function_call::bad_function_call(), callable::~callable(), and function::~function() are superfluous.

  2. If you actually have to declare a special member-function, explicitly default it in-class where that is enough:
    callable_base::~ callable_base(), and function::function() should be explicitly defaulted.

  3. Consider investing in small-object-optimization. Yes, it will make the code a bit more complicated, but on the flip-side it should result in much improved efficiency. Also, it is mandatory.

  4. Consider using the null-object-pattern to pare down on checks. There is considerable synergy with the previous point.

  5. You can replace (most of) your uses of static_cast<bool>() with double-negation.

  6. Personally, I prefer a bigger indentation-step, but whatever. Also, not having colon : followed by any whitespace takes some getting used to.

answered Jun 23, 2019 at 16:04
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.