Impossibly fast delegate in C++11

  • This legendary C++ delegate article can be easily converted into C++11, without the need for fancy preprocessor magic in the original. I'd like to know if I got all the necessary C++11 nuances right. Suggestions?



    #pragma once
    #ifndef DELEGATE_HPP
    # define DELEGATE_HPP

    #include <cassert>

    #include <memory>

    #include <new>

    #include <type_traits>

    #include <utility>

    template <typename T> class delegate;

    template<class R, class ...A>
    class delegate<R (A...)>
    {
    using stub_ptr_type = R (*)(void*, A&&...);

    delegate(void* const o, stub_ptr_type const m) noexcept :
    object_ptr_(o),
    stub_ptr_(m)
    {
    }

    public:
    delegate() = default;

    delegate(delegate const&) = default;

    delegate(delegate&&) = default;

    delegate(::std::nullptr_t const) noexcept : delegate() { }

    template <class C, typename =
    typename ::std::enable_if< ::std::is_class<C>{}>::type>
    explicit delegate(C const* const o) noexcept :
    object_ptr_(const_cast<C*>(o))
    {
    }

    template <class C, typename =
    typename ::std::enable_if< ::std::is_class<C>{}>::type>
    explicit delegate(C const& o) noexcept :
    object_ptr_(const_cast<C*>(&o))
    {
    }

    template <class C>
    delegate(C* const object_ptr, R (C::* const method_ptr)(A...))
    {
    *this = from(object_ptr, method_ptr);
    }

    template <class C>
    delegate(C* const object_ptr, R (C::* const method_ptr)(A...) const)
    {
    *this = from(object_ptr, method_ptr);
    }

    template <class C>
    delegate(C& object, R (C::* const method_ptr)(A...))
    {
    *this = from(object, method_ptr);
    }

    template <class C>
    delegate(C const& object, R (C::* const method_ptr)(A...) const)
    {
    *this = from(object, method_ptr);
    }

    template <
    typename T,
    typename = typename ::std::enable_if<
    !::std::is_same<delegate, typename ::std::decay<T>::type>{}
    >::type
    >
    delegate(T&& f) :
    store_(operator new(sizeof(typename ::std::decay<T>::type)),
    functor_deleter<typename ::std::decay<T>::type>),
    store_size_(sizeof(typename ::std::decay<T>::type))
    {
    using functor_type = typename ::std::decay<T>::type;

    new (store_.get()) functor_type(::std::forward<T>(f));

    object_ptr_ = store_.get();

    stub_ptr_ = functor_stub<functor_type>;

    deleter_ = deleter_stub<functor_type>;
    }

    delegate& operator=(delegate const&) = default;

    delegate& operator=(delegate&&) = default;

    template <class C>
    delegate& operator=(R (C::* const rhs)(A...))
    {
    return *this = from(static_cast<C*>(object_ptr_), rhs);
    }

    template <class C>
    delegate& operator=(R (C::* const rhs)(A...) const)
    {
    return *this = from(static_cast<C const*>(object_ptr_), rhs);
    }

    template <
    typename T,
    typename = typename ::std::enable_if<
    !::std::is_same<delegate, typename ::std::decay<T>::type>{}
    >::type
    >
    delegate& operator=(T&& f)
    {
    using functor_type = typename ::std::decay<T>::type;

    if ((sizeof(functor_type) > store_size_) || !store_.unique())
    {
    store_.reset(operator new(sizeof(functor_type)),
    functor_deleter<functor_type>);

    store_size_ = sizeof(functor_type);
    }
    else
    {
    deleter_(store_.get());
    }

    new (store_.get()) functor_type(::std::forward<T>(f));

    object_ptr_ = store_.get();

    stub_ptr_ = functor_stub<functor_type>;

    deleter_ = deleter_stub<functor_type>;

    return *this;
    }

    template <R (* const function_ptr)(A...)>
    static delegate from() noexcept
    {
    return { nullptr, function_stub<function_ptr> };
    }

    template <class C, R (C::* const method_ptr)(A...)>
    static delegate from(C* const object_ptr) noexcept
    {
    return { object_ptr, method_stub<C, method_ptr> };
    }

    template <class C, R (C::* const method_ptr)(A...) const>
    static delegate from(C const* const object_ptr) noexcept
    {
    return { const_cast<C*>(object_ptr), const_method_stub<C, method_ptr> };
    }

    template <class C, R (C::* const method_ptr)(A...)>
    static delegate from(C& object) noexcept
    {
    return { &object, method_stub<C, method_ptr> };
    }

    template <class C, R (C::* const method_ptr)(A...) const>
    static delegate from(C const& object) noexcept
    {
    return { const_cast<C*>(&object), const_method_stub<C, method_ptr> };
    }

    template <typename T>
    static delegate from(T&& f)
    {
    return ::std::forward<T>(f);
    }

    static delegate from(R (* const function_ptr)(A...))
    {
    return function_ptr;
    }

    template <class C>
    using member_pair =
    ::std::pair<C* const, R (C::* const)(A...)>;

    template <class C>
    using const_member_pair =
    ::std::pair<C const* const, R (C::* const)(A...) const>;

    template <class C>
    static delegate from(C* const object_ptr,
    R (C::* const method_ptr)(A...))
    {
    return member_pair<C>(object_ptr, method_ptr);
    }

    template <class C>
    static delegate from(C const* const object_ptr,
    R (C::* const method_ptr)(A...) const)
    {
    return const_member_pair<C>(object_ptr, method_ptr);
    }

    template <class C>
    static delegate from(C& object, R (C::* const method_ptr)(A...))
    {
    return member_pair<C>(&object, method_ptr);
    }

    template <class C>
    static delegate from(C const& object,
    R (C::* const method_ptr)(A...) const)
    {
    return const_member_pair<C>(&object, method_ptr);
    }

    void reset() { stub_ptr_ = nullptr; store_.reset(); }

    void reset_stub() noexcept { stub_ptr_ = nullptr; }

    void swap(delegate& other) noexcept { ::std::swap(*this, other); }

    bool operator==(delegate const& rhs) const noexcept
    {
    return (object_ptr_ == rhs.object_ptr_) && (stub_ptr_ == rhs.stub_ptr_);
    }

    bool operator!=(delegate const& rhs) const noexcept
    {
    return !operator==(rhs);
    }

    bool operator<(delegate const& rhs) const noexcept
    {
    return (object_ptr_ < rhs.object_ptr_) ||
    ((object_ptr_ == rhs.object_ptr_) && (stub_ptr_ < rhs.stub_ptr_));
    }

    bool operator==(::std::nullptr_t const) const noexcept
    {
    return !stub_ptr_;
    }

    bool operator!=(::std::nullptr_t const) const noexcept
    {
    return stub_ptr_;
    }

    explicit operator bool() const noexcept { return stub_ptr_; }

    R operator()(A... args) const
    {
    // assert(stub_ptr);
    return stub_ptr_(object_ptr_, ::std::forward<A>(args)...);
    }

    private:
    friend struct ::std::hash<delegate>;

    using deleter_type = void (*)(void*);

    void* object_ptr_;
    stub_ptr_type stub_ptr_{};

    deleter_type deleter_;

    ::std::shared_ptr<void> store_;
    ::std::size_t store_size_;

    template <class T>
    static void functor_deleter(void* const p)
    {
    static_cast<T*>(p)->~T();

    operator delete(p);
    }

    template <class T>
    static void deleter_stub(void* const p)
    {
    static_cast<T*>(p)->~T();
    }

    template <R (*function_ptr)(A...)>
    static R function_stub(void* const, A&&... args)
    {
    return function_ptr(::std::forward<A>(args)...);
    }

    template <class C, R (C::*method_ptr)(A...)>
    static R method_stub(void* const object_ptr, A&&... args)
    {
    return (static_cast<C*>(object_ptr)->*method_ptr)(
    ::std::forward<A>(args)...);
    }

    template <class C, R (C::*method_ptr)(A...) const>
    static R const_method_stub(void* const object_ptr, A&&... args)
    {
    return (static_cast<C const*>(object_ptr)->*method_ptr)(
    ::std::forward<A>(args)...);
    }

    template <typename>
    struct is_member_pair : std::false_type { };

    template <class C>
    struct is_member_pair< ::std::pair<C* const,
    R (C::* const)(A...)> > : std::true_type
    {
    };

    template <typename>
    struct is_const_member_pair : std::false_type { };

    template <class C>
    struct is_const_member_pair< ::std::pair<C const* const,
    R (C::* const)(A...) const> > : std::true_type
    {
    };

    template <typename T>
    static typename ::std::enable_if<
    !(is_member_pair<T>{} ||
    is_const_member_pair<T>{}),
    R
    >::type
    functor_stub(void* const object_ptr, A&&... args)
    {
    return (*static_cast<T*>(object_ptr))(::std::forward<A>(args)...);
    }

    template <typename T>
    static typename ::std::enable_if<
    is_member_pair<T>{} ||
    is_const_member_pair<T>{},
    R
    >::type
    functor_stub(void* const object_ptr, A&&... args)
    {
    return (static_cast<T*>(object_ptr)->first->*
    static_cast<T*>(object_ptr)->second)(::std::forward<A>(args)...);
    }
    };

    namespace std
    {
    template <typename R, typename ...A>
    struct hash<::delegate<R (A...)> >
    {
    size_t operator()(::delegate<R (A...)> const& d) const noexcept
    {
    auto const seed(hash<void*>()(d.object_ptr_));

    return hash<typename ::delegate<R (A...)>::stub_ptr_type>()(
    d.stub_ptr_) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
    };
    }

    #endif // DELEGATE_HPP


    Example use:



    #include <iostream>

    #include "delegate.hpp"

    struct A
    {
    void foo(int a)
    {
    std::cout << "method got: " << a << std::endl;
    }
    };

    void foo(int a)
    {
    std::cout << "function got: " << a << std::endl;
    }

    int main(int argc, char* argv[])
    {
    auto d1(delegate<void (int)>::from<foo>());

    A a;
    auto d2(delegate<void (int)>::from<A, &A::foo>(&a));
    auto d3(delegate<void (int)>{foo});
    auto d4(delegate<void (int)>(&a, &A::foo));

    d1(1);
    d2(2);
    d3(3);
    d4(4);

    int b(2);

    auto dx(delegate<void ()>(
    [b](){std::cout << "hello world: " << b << std::endl;}));

    dx();

    return 0;
    }

    Is there any reason for using this instead of `std::function`?

    @ony It is usually faster, especially when using pointer to member template parameters.

    is there any reason to have slower implementation of `std::function` in C++ lib/run-time? As well there is code `return from(object, method_ptr{ return (object.*method_ptr)(args...); }); }`

    @ony Benchmark and you'll see. If you like it, use it. Is there something wrong with the code? Does it fail to compile? If so, please post compile error. The code is C++11. You can also post a critique as an answer.

    Last time I checked `std::function` is quite fast compared to the impossibly fast delegate class. I did this benchmark by using my library awhile ago with Apple clang 4.1 (although the benchmark for std::function isn't actually there, I'm sure you could test it out yourself using your own code or even mine).

    @miguel.martin Thanks, I don't really present the class here as some kind of holy grail delegate, but want to gather comments on the C++11 style I use. In my compiler setup `std::function` is about 10x slower than the impossibly fast delegate (when using template function pointer parameters).

    Hey, I'd like to use this in one of my project. Is this the definitive version or have you improved it further?

    @VittorioRomeo Hope it's going to work for you. I don't know, if this is the best solution.

    @user1095108: check my answer below - so far so good

    Hi, can you please explain a bit how you came up with your hash function for this?

    @user1095108 You said *"In my compiler setup, `std::function` is about 10x slower..."*. What compiler/library is that? I've spent hours trying to beat gcc's libstdc++ implementation and everything I tried was exactly the same speed as `std::function`.

    @doug65536 That's the beauty of open-source development. What I wrote was true at the time, but it may not be anymore. Both `g++` and `libstdc++` change over time. You can use `::std::function`, if you think it is better than this delegate. I've had bad experiences with it, when doing some `OpenGL` programming. It showed high in the profiler output and so I've looked around for alternatives. In hindsight, I should not have bothered with it, though the subject is fascinating and has a long history.

    @user1095108 If you were using MSVC, I remember a bug on microsoft connect complaining of an unbelievably slow `std::function` implementation. I tried to find it to quote the link, but it seems they purge old bugs or their search is really badly implemented (like their `std::function` implementation ;). Something like "std::function copies the object more than 10x".

    The repeated, and ongoing edits to this question have completely invalidated the existing answers. This question has turned in to a mess of revisions, interleaved history, and is a very poor example of how Code Review questions should be managed. See Can I edit my own question to include revised code? Also, how to handle iterative reviews?

  • Kerrek SB

    Kerrek SB Correct answer

    9 years ago

    It looks by and large OK. Just some nitpicks




    • the default constructor could just be delegate() = default;, or otherwise initialize all members... or remove it entirely if it makes no sense.


    • the copy constructor should use initialization, not assignment.


    • swap should return void.


    • Assignment should pass-by-value:



      delegate& operator=(delegate rhs) { rhs.swap(*this); return *this; }

    • Invocation should use arbitrary arguments and forwarding:



      template <typename ...B>
      R operator()(B &&... b)
      {
      return (*stub_ptr)(object_ptr, std::forward<B>(b)...);
      }


      In fact, you should add enable_if with some variadic version of is_constructible<A, B>... to the operator so you don't create impossible overloads.



    Thanks for your comments! I think the forwarding is still shaky, as the static function stubs still make copies of their arguments. Can this be fixed?

    @user1095108: I thought the invocation was `auto d = delegate::from_function();` -- there's no room for arguments, is there?

    What about lambdas? Can we turn them into delegates?

    @WarrenSeine I've added lambda support.

    Why should assignment pass by value?

    @doug65536 to avoid having to have `const T&` and `T&&` versions of assignment; passing by value will choose move construction or copy construction automatically for `rhs`.

License under CC-BY-SA with attribution


Content dated before 7/24/2021 11:53 AM

Tags used