3

I'm porting FastDelegate to C++0x using variadic templates.

    #include "FastDelegate.h"

    template<class R=fastdelegate::detail::DefaultVoid, class ...P>
    class fast_delegate_base {
    private:
        typedef typename fastdelegate::detail::DefaultVoidToVoid<R>::type desired_ret_t;
        typedef desired_ret_t (*static_func_ptr)(P...);
        typedef R (*unvoid_static_func_ptr)(P...);
        typedef R (fastdelegate::detail::GenericClass::*generic_mem_fn)(P...);
        typedef fastdelegate::detail::ClosurePtr<generic_mem_fn, static_func_ptr, unvoid_static_func_ptr> closure_t;
        closure_t closure_;
    public:
        // Typedefs to aid generic programming
        typedef fast_delegate_base type;

        // Construction and comparison functions
        fast_delegate_base() { clear(); }

        fast_delegate_base(const fast_delegate_base &x)
        {
            closure_.CopyFrom(this, x.closure_);
        }

        void operator = (const fast_delegate_base &x)
        {
            closure_.CopyFrom(this, x.closure_);
        }
        bool operator ==(const fast_delegate_base &x) const
        {
            return closure_.IsEqual(x.closure_);
        }
        bool operator !=(const fast_delegate_base &x) const
        {
            return !closure_.IsEqual(x.closure_);
        }
        bool operator <(const fast_delegate_base &x) const
        {
            return closure_.IsLess(x.closure_);
        }
        bool operator >(const fast_delegate_base &x) const
        {
            return x.closure_.IsLess(closure_);
        }

        // Binding to non-const member functions
        template<class X, class Y>
        fast_delegate_base(Y *pthis, desired_ret_t (X::* function_to_bind)(P...) )
        {
            closure_.bindmemfunc(fastdelegate::detail::implicit_cast<X*>(pthis), function_to_bind);
        }

        template<class X, class Y>
        inline void bind(Y *pthis, desired_ret_t (X::* function_to_bind)(P...))
        {
            closure_.bindmemfunc(fastdelegate::detail::implicit_cast<X*>(pthis), function_to_bind);
        }

        // Binding to const member functions.
        template<class X, class Y>
        fast_delegate_base(const Y *pthis, desired_ret_t (X::* function_to_bind)(P...) const)
        {
            closure_.bindconstmemfunc(fastdelegate::detail::implicit_cast<const X*>(pthis), function_to_bind);
        }

        template<class X, class Y>
        inline void bind(const Y *pthis, desired_ret_t (X::* function_to_bind)(P...) const)
        {
            closure_.bindconstmemfunc(fastdelegate::detail::implicit_cast<const X *>(pthis), function_to_bind);
        }

        // Static functions. We convert them into a member function call.
        // This constructor also provides implicit conversion
        fast_delegate_base(desired_ret_t (*function_to_bind)(P...) )
        {
            bind(function_to_bind);
        }

        // for efficiency, prevent creation of a temporary
        void operator = (desired_ret_t (*function_to_bind)(P...) )
        {
            bind(function_to_bind);
        }

        inline void bind(desired_ret_t (*function_to_bind)(P...))
        {
            closure_.bindstaticfunc(this, &fast_delegate_base::invoke_static_func, function_to_bind);
        }

        // Invoke the delegate
        template<typename ...A>
        R operator()(A&&... args) const
        {
            return (closure_.GetClosureThis()->*(closure_.GetClosureMemPtr()))(std::forward<A>(args)...);
        }
        // Implicit conversion to "bool" using the safe_bool idiom

    private:
        typedef struct safe_bool_struct
        {
            int a_data_pointer_to_this_is_0_on_buggy_compilers;
            static_func_ptr m_nonzero;
        } useless_typedef;
        typedef static_func_ptr safe_bool_struct::*unspecified_bool_type;
    public:
        operator unspecified_bool_type() const { return empty()? 0: &safe_bool_struct::m_nonzero; }
        // necessary to allow ==0 to work despite the safe_bool idiom
        inline bool operator==(static_func_ptr funcptr) { return closure_.IsEqualToStaticFuncPtr(funcptr); }
        inline bool operator!=(static_func_ptr funcptr) { return !closure_.IsEqualToStaticFuncPtr(funcptr); }
        // Is it bound to anything?
        inline bool operator ! () const { return !closure_; }
        inline bool empty() const { return !closure_; }
        void clear() { closure_.clear();}
        // Conversion to and from the DelegateMemento storage class
        const fastdelegate::DelegateMemento & GetMemento() { return closure_; }
        void SetMemento(const fastdelegate::DelegateMemento &any) { closure_.CopyFrom(this, any); }

    private:
        // Invoker for static functions
        R invoke_static_func(P... args) const
        {
            return (*(closure_.GetStaticFunction()))(args...);
        }
    };

    // fast_delegate<> is similar to std::function, but it has comparison operators.
    template<typename _Signature>
    class fast_delegate;

    template<typename R, typename ...P>
    class fast_delegate<R(P...)> : public fast_delegate_base<R, P...>
    {
    public:
        typedef fast_delegate_base<R, P...> BaseType;

        fast_delegate() : BaseType() { }

        template<class X, class Y>
        fast_delegate(Y * pthis, R (X::* function_to_bind)(P...))
            : BaseType(pthis, function_to_bind)
        { }

        template<class X, class Y>
        fast_delegate(const Y *pthis, R (X::* function_to_bind)(P...) const)
            : BaseType(pthis, function_to_bind)
        { }

        fast_delegate(R (*function_to_bind)(P...))
            : BaseType(function_to_bind)
        { }

        void operator = (const BaseType &x)
        {
            *static_cast<BaseType*>(this) = x;
        }
    };

But, one of the limitations of my implementation is, when using non-member functions, and in case that function accepts parameter(s) by value, an extra value copy for each parameters take place. I assume that this occurs between fast_delegate_base::operator()() and fast_delegate_base::invoke_static_func().

I tried to make fast_delegate_base::invoke_static_func() to accept Rvalue parameters, but failed.

For example:

class C1
{
public:
    C1() { printf("C1()\n"); }
    ~C1() { printf("~C1()\n"); }
    C1(const C1&)
    {
        printf("C1(const C1&)\n");
    }

    int test(int t) const
    {
        printf("C1::test(%d)\n", t);
        return 1;
    }
};

int test(C1 c)
{
    c.test(1234);
    return 1;
}

// ...

C1 c1;
fast_delegate<int(C1)> t1(test);
t1(c1);

Result of this code is:

C1()
C1(const C1&)
C1(const C1&)
C1::test(1234)
~C1()
~C1()
~C1()

Do you have any idea to avoid this extra value copy?

Daniel K.
  • 947
  • 3
  • 11
  • 21
  • The code isn't complete, as far as I can tell, i.e. I can't reproduce the issue easily. One copy is created for the value being passed. I suspect that the intermediate forwarding does the other one using the same signature as the function being called. That said: why not use `std::function`? – Dietmar Kühl Feb 10 '12 at 23:26
  • Because std::function<> does not support comparison: operator ==(). – Daniel K. Feb 11 '12 at 00:18
  • Can you try boiling your code way down to a compact example of good C++98 working how you want and the C++11 version that does the extra copy? – John Zwinck Feb 11 '12 at 00:27
  • I see perfect forwarding implemented in `fast_delegate_base::operator()`, but not anywhere else -- is this intentional? From my (naive) view, it seems wrong. – ildjarn Feb 11 '12 at 00:53
  • @JohnZwinck I assumed, as you did, that this was a regression from the '98 implementation. But the '98 implementation actually takes 3 copies, as does std::function (w/gcc 4.6.1). This implementation avoids one copy, the first one, by using perfect forwarding on operator(), the original didn't. – je4d Feb 11 '12 at 01:51
  • All this is unnecessary now that C++11 added lambdas. – Ben Voigt Feb 11 '12 at 06:34
  • I intended to make a general event-emitter (or, singal-slot: http://en.wikipedia.org/wiki/Signals_and_slots) that support functions , lambdas and function objects. Because std::function<> does not support equality test, I tried FastDelegate. But, later on I found out that FastDelegate did not support stated lambdas (which captures variables) because it cannot be converted to function pointer. I mean, I know that my C++ 11 port for FastDelegate cannot be used for what I needed. But, I was just curious about a way to avoid extra copy in my implementation. Sorry for the confusion. – Daniel K. Feb 11 '12 at 08:29
  • Totally unrelated to the original question: have you ever finished the C++11 version port of FastDelegate? It would be great to use it – Vittorio Romeo Jan 20 '13 at 17:14

1 Answers1

1

It looks to me like this copy is inherent in the design of the class, specifically the existence of invoke_static_func.

From what I can see, this is a proxy to normalize static functions and member functions into just member functions, so they every dispatch can be done as a member function call. The only difference is that the member is the fast_delegate_base instance rather than an instance of whatever class the target function is a member of.

So there's an extra call frame when calling static functions, and to get rid of that extra copy you would need to make the extra call frame (invoke_static_func) take its parameter by a reference (ignore for now the consequences of this if the argument type is not a value).

Unfortunately, invoke_static_func needs to be called via a function pointer which has an argument list containing value types, so operator() is forced to make a copy in order to invoke the function pointer (i.e. to invoke invoke_static_func). Making invoke_static_func take parameters by reference doesn't help, because it still has to be invoked via a function pointer that does not have reference argument types.

And there's no way invoke_static_func can avoid making a copy to call test(C1), that's just a simple call by value - so you need both copies to make this design work.


To explain it from a different perspective, thin of it in terms of pure C:

Operator() needs to call a function func (this_ptr, arg_1, arg_2, arg_3). The target function will expect these parameters to be in particular registers or particular stack locations depending on their position in the argument list and size.

But a static function does not have the magic first 'this' parameter, its signature is just func(arg_1, arg_2, arg_3). So it expects all the other arguments to be in different registers and/or stack locations than the corresponding member function does. So you need that copy to move the arguments into the right registers/stack locations to comply with the calling convention for the static function.

Which basically, means you can't avoid that second copy for a static function with this design.


However... you may be able to improve on this by some crafty template metaprogramming to apply std::move to value type arguments in the implementation of invoke_static_func, reducing your call overhead to a copy and a move, which is almost as good as just one copy.

I'll update this answer if and when I figure whether that's possible (and if so how).


Edit

Something like this should do the trick:

template <bool IsClass, class U>
struct move_if_class
{
    template <typename T>
    T&& operator()(const T& t) { return std::move(const_cast<T&>(t)); }
};

template <class T>
struct move_if_class<false,T>
{
    T&& operator()(typename std::remove_reference<T>::type& t) { return std::forward<T>(t); }
    T&& operator()(typename std::remove_reference<T>::type&& t) { return std::forward<T>(t); }
};

R invoke_static_func(P... args) const
{
    return (*(closure_.GetStaticFunction()))(move_if_class<std::is_class<P>::value,P>()(args)...);
}   

And after adding a move c'tor:

C1()
C1(const C1&)
C1(C1&&)
C1::test(1234)
~C1()
~C1()
~C1()
je4d
  • 7,628
  • 32
  • 46
  • Much better in case passed objects support move semantic. Let me wait for others a little longer :D Thanks. – Daniel K. Feb 11 '12 at 07:59
  • @DanielK. Having given it a bit more thought, I think it may be possible to get rid of that copy - you'd have to do some magic on the signature of the internal function pointer to convert all class type arguments to const lvalue references, and introduce a `invoke_member_func` function with that modified signature that calls the target member function. That way the `invoke_[member|static]_func` functions never take a copy, and a single copy is always taken when calling the target. Good luck with it :-) – je4d Feb 11 '12 at 23:31