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
\$\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.

\$\endgroup\$
0

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.