注意,使用最新的boost需要进行修改:Just replaceboost::ct_ifwithboost::mpl::if_c(and#include <boost/mpl/if.hpp>) in Jae's Fast Delegate code.

Introduction

There have been several C++ delegates which declared themselves as a 'fast' or 'fastest' delegate, whileBoost.Functionand its siblings,Boost.BindandBoost.Mem_fn, were adopted as part ofC++ Standards Committee'sLibrary Technical Report (TR1). So, what are those called 'fast' or 'fastest' delegates and how much 'faster' are they thanBoost.Function?

The prefix 'fast' in the term 'fast(est) delegate' means either 'fast' invocation or 'fast' copy, or both. But, I believe, what is really an issue between the two when using the 'non-fast'Boost.Functionis more likely its awful copy performance. This is due to the expensive heap memory allocation that is required to store the member function and the bound object on which member function call is made. So, 'fast' delegate often refers to a delegate that does not require heap memory allocation for storing the member function and the bound object. In C++, as an object oriented programming paradigm, use of delegate or closure for the member function and the bound object is one of the most frequently occurring practices. Thus, a 'fast' delegate can 'boost' the performance by far in some situations.

The following four graphs are the result of the invocation speed comparison among three fast delegates andBoost.Functionin the various function call scenarios. See the '%FD_ROOT%/benchmark' for details.

Invocation speed benchmark #01

Invocation speed benchmark #02

Invocation speed benchmark #03

Invocation speed benchmark #04

The following two graphs are the result of the copy speed comparison among three fast delegates andBoost.Function. For a bound member function call, it was found thatBoost.Functioncan take 150 times longer than the fastest. The result may vary based on the benchmark platform and environment, but it is obvious that the copy performance ofBoost.Functionis not acceptable in certain cases.

Copy speed benchmark - Debug mode

Copy speed benchmark - Release mode

In spite of the prominent speed boost of the fast delegates in specific cases, it is not comfortable for many programmers to switch and start using the fast delegates. This is because their features are not as rich asthose which Boost.Functionand its siblings provide, and we are already accustomed to usingBoosts. These fast delegates support very limited types of callable entities to store and mostly do not support the storing of a function object, which is another frequently occurring practice in C++.

I had implemented a fast delegate some time ago, but it was not as fast as other fast delegates nor as C++ Standard compliant as I thought it was. I actually patched it to be C++ Standard compliant later. This is the second version, but it is completely re-implemented from the scratch. The old version is obsolete. It is another 'fast' delegate, but it is also aBoost.Function'drop-in' replacement and more. I say 'more' because it supports the multicast feature which is missing in the most of C++ delegates currently available. It is not like an ancillary class to support multicast, but one class instance acts as single cast and multicast on demand, without any runtime performance penalty.FD.Delegatecan be thought of as an aggregation ofBoost.Functionand its siblings (Boost.BindandBoost.Mem_fn) plus some features fromBoost.Signals. See the 'Delegates Comparison Chart' at the end of the article for particulars.

Using the code

As stated previously,FD.Delegateis aBoost.Function'drop-in' replacement. So it is reasonable to refer to the online documentation ofBoost.Functionand especiallyBoost.Functiontutorialfor features ofFD.Delegate. Just make sure to add '%FD_ROOT%/include' as a system include directory.

- Example #1 fromBoost.Function.

#include <iostream>
#include <fd/delegate.hpp>

struct int_div
{
    float operator()(int x, int y) const { return ((float)x)/y; };
};

int main()
{
    fd::delegate<float (int, int)> f;
    f = int_div();

    std::cout << f(5, 3) << std::endl; // 1.66667

    return 0;
}

- Example #2 fromBoost.Function.

#include <iostream>
#include <fd/delegate.hpp>

void do_sum_avg(int values[], int n, int& sum, float& avg)
{
    sum = 0;
    for (int i = 0; i < n; i++)
        sum += values[i];
    avg = (float)sum / n;
}

int main()
{
    // The second parameter should be int[], but some compilers (e.g., GCC)
    // complain about this
    fd::delegate<void (int*, int, int&, float&)> sum_avg;

    sum_avg = &do_sum_avg;

    int values[5] = { 1, 1, 2, 3, 5 };
    int sum;
    float avg;
    sum_avg(values, 5, sum, avg);

    std::cout << "sum = " << sum << std::endl;
    std::cout << "avg = " << avg << std::endl;
    return 0;
}

FD.Delegatesupports multicast and uses C#'s multicast syntax,operator +=andoperator -=.

#include <iostream>
#include <fd/delegate/delegate2.hpp>

struct print_sum
{
    void operator()(int x, int y) const { std::cout << x+y << std::endl; }
};

struct print_product
{
    void operator()(int x, int y) const { std::cout << x*y << std::endl; }
};

int main()
{
    fd::delegate2<void, int, int> dg;

    dg += print_sum();
    dg += print_product();

    dg(3, 5); // prints 8 and 15

    return 0;
}

While a function pointer is equality comparable, a function object is not quite determinant at compile-time, whether equality comparable or not. This fact makesoperator -=pretty much useless for removing a function object from multicast.FD.Delegatehasadd()andremove()member function pairs to remedy the issue.add()returns an instance offd::multicast::tokenwhich can be used to remove the added delegate(s).

#include <iostream>
#include <fd/delegate.hpp>
#include <cassert>

struct print_sum
{
    void operator()(int x, int y) const { std::cout << x+y << std::endl; }
};

struct print_product
{
    void operator()(int x, int y) const { std::cout << x*y << std::endl; }
};

struct print_difference
{
    void operator()(int x, int y) const { std::cout << x-y << std::endl; }
};

struct print_quotient
{
    void operator()(int x, int y) const { std::cout << x/-y << std::endl; }
};

int main()
{
    fd::delegate2<void, int, int> dg;

    dg += print_sum();
    dg += print_product();

    dg(3, 5);

    fd::multicast::token print_diff_tok = dg.add(print_difference());

    // print_diff_tok is still connected to dg
    assert(print_diff_tok.valid());

    dg(5, 3); // prints 8, 15, and 2

    print_diff_tok.remove(); // remove the print_difference delegate

    dg(5, 3);  // now prints 8 and 15, but not the difference

    assert(!print_diff_tok.valid()); // not connected anymore
    {
        fd::multicast::scoped_token t = dg.add(print_quotient());
        dg(5, 3); // prints 8, 15, and 1
    } // t falls out of scope, so print_quotient is not a member of dg

    dg(5, 3); // prints 8 and 15

    return 0;
}

It has been one of the main concerns for a multicast delegate how to manage multiple return values.Combiner interfaceofBoost.Signalshas been adopted, but has slightly different usage and syntax. The type of the combiner interface is not a part ofFD.Delegatetype, although it is forBoost.Signals, as the form of the template parameter when declaring a signal variable. Instead,FD.Delegatehas a special function call operator which takes the instance of the combiner interface as the last function call argument.

#include <algorithm>
#include <iostream>
#include <fd/delegate.hpp>

template<typename T>
struct maximum
{
    typedef T result_type;

    template<typename InputIterator>
    T operator()(InputIterator first, InputIterator last) const
    {
        if(first == last)
            throw std::runtime_error("Cannot compute maximum of zero elements!");
        return *std::max_element(first, last);
  }
};

template<typename Container>
struct aggregate_values
{
    typedef Container result_type;

    template<typename InputIterator>
    Container operator()(InputIterator first, InputIterator last) const
    {
        return Container(first, last);
    }
};

int main()
{
    fd::delegate2<int, int, int> dg_max;
    dg_max += std::plus<int>();
    dg_max += std::multiplies<int>();
    dg_max += std::minus<int>();
    dg_max += std::divides<int>();

    std::cout << dg_max(5, 3, maximum<int>()) << std::endl; // prints 15

    std::vector<int> vec_result = dg_max(5, 3, 
        aggregate_values<std::vector<int> >());
    assert(vec_result.size() == 4);

    std::cout << vec_result[0] << std::endl; // prints 8
    std::cout << vec_result[1] << std::endl; // prints 15
    std::cout << vec_result[2] << std::endl; // prints 2
    std::cout << vec_result[3] << std::endl; // prints 0

    return 0;
}

Under the hood

Part A: storing a function pointer for later invocation without requiring heap memory allocation.

According to C++ standards, a function pointer -- both free function pointer and member function pointer -- cannot be converted or stored into avoid *. A function pointer may be converted into a function pointer of a different type signature, however, the result of such conversion cannot be used; it can only be converted back. The size of the member function varies over the different platforms from 4 bytes to 16 bytes. To avoid heap allocation to store the member function, some well-known template meta programming techniques have been adapted. These permit a member function pointer whose size is less than or equal to the size of the predefined generic member function pointer to be stored without heap memory allocation. The stored generic member function pointer is restored back to its original member function type before use.

typedef void generic_fxn();

class alignment_dummy_base1 { };
class alignment_dummy_base2 { };

class alignment_dummy_s : alignment_dummy_base1 { };                         
    // single inheritance.
class alignment_dummy_m : alignment_dummy_base1, alignment_dummy_base2 { };  
    // multiple inheritance.
class alignment_dummy_v : virtual alignment_dummy_base1 { };                 
    // virtual inheritance.
class alignment_dummy_u;                                                     
    // unknown (incomplete).

typedef void (alignment_dummy_s::*mfn_ptr_s)();  
    // member function pointer of single inheritance class.
typedef void (alignment_dummy_m::*mfn_ptr_m)();  
    // member function pointer of multiple inheritance class.
typedef void (alignment_dummy_v::*mfn_ptr_v)();  
    // member function pointer of virtual inheritance class.
typedef void (alignment_dummy_u::*mfn_ptr_u)();  
    // member function pointer of unknown (incomplete) class.

typedef void (alignment_dummy_m::*generic_mfn_ptr)();

union max_align_for_funtion_pointer
{
    void const * dummy_vp;
    generic_fxn * dummy_fp;
    boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_s ) ),
      generic_mfn_ptr, mfn_ptr_s>::type dummy_mfp1;
    boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_m ) ),
      generic_mfn_ptr, mfn_ptr_m>::type dummy_mfp2;
    boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_v ) ),
      generic_mfn_ptr, mfn_ptr_v>::type dummy_mfp3;
    boost::ct_if<( sizeof( generic_mfn_ptr ) < sizeof( mfn_ptr_u ) ),
      generic_mfn_ptr, mfn_ptr_u>::type dummy_mfp4;
};

BOOST_STATIC_CONSTANT( unsigned, 
    any_fxn_size = sizeof( max_align_for_funtion_pointer ) );

union any_fxn_pointer
{
    void const * obj_ptr;
    generic_fxn * fxn_ptr;
    generic_mfn_ptr mfn_ptr;
    max_align_for_funtion_pointer m_;
};

A member function pointer whose size is less than or equal toany_fxn_sizeis stored intoany_fxn_pointer.any_fxn_pointeris implemented to make it able to store one out of three different pointer types -- avoiddata pointer, a function pointer, or a member function pointer -- whose size is less than a function pointer to the member of a multiple inherited class. Only one pointer type is stored at one specific time. Care has been taken regarding the misalignment issue, which may cause undefined behavior according to the C++ standard, by applying the specialized version of the well-known max alignment union trickery.

void hello(int, float) { }
typedef void (*MyFxn)(int, float);

struct foobar
{
    void foo(int, float) { }
};
typedef void (foobar::*MyMfn)(int, float);


void test1(any_fxn_pointer any)
{
    ( *reinterpret_cast<MyFxn>( any.fxn_ptr ) )( 1, 1.0f );
}

void test2(any_fxn_pointer any, foobar * pfb)
{
    ( pfb->*reinterpret_cast<MyMfn>( any.mfn_ptr ) )( 1, 1.0f );
}

void main()
{
    any_fxn_pointer any;
    any.fxn_ptr = reinterpret_cast<generic_fxn *>( &hello );

    test1( any );

    foobar fb;
    any.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( &foobar::foo );
    test2( any, &fb );
}

When the size of a member function pointer is greater thanany_fxn_size, takes for an example when storing a member function pointer tovirtualinherited class in MSVC, it is stored by allocating heap memory in the same way thatBoost.Functiondoes, as a non-fast delegate. However, in real world practices,virtualinheritance is rarely used.

template<typename U, typename T>
void bind(UR (U::*fxn)(int, float), T t)
{
    struct select_stub
    {
        typedef void (U::*TFxn)(int, float);
        typedef typename boost::ct_if<( sizeof( TFxn ) <= any_fxn_size ),
        typename impl_class::fast_mfn_delegate,
        typename impl_class::normal_mfn_delegate
        >::type type;
    };

    select_stub::type::bind( *this, fxn, t, );
}

Part B: using any_fxn_pointer

any_fxn_pointer is used for storing an arbitrary function pointer with the class template. This is done in order to erase the type while storing the function and to restore the original type safely when required later. Sergey Ryazanov demonstrated inhis articlethat a C++ standard compliant fast delegate for member function can be implemented using the class template with a non-type member function template parameter. The sample below shows a rough idea of how it had been implemented.

class delegate
{
    typedef void (*invoke_stub)(void const *, int);

    void const * obj_ptr_;
    invoke_stub stub_ptr_;

    template<typename T, void (T::*Fxn)(int)>
    struct mem_fn_stub
    {
        static void invoke(void const * obj_ptr, int a0)
        {
            T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) );
            (obj->*Fxn)( a0 );
        }
    };

    template<typename T, void (T::*Fxn)(int) const>
    struct mem_fn_const_stub
    {
        static void invoke(void const * obj_ptr, int a0)
        {
            T const * obj = static_cast<T const *>( obj_ptr );
            (obj->*Fxn)( a0 );
        }
    };

    template<void (*Fxn)(int)>
    struct function_stub
    {
        static void invoke(void const *, int a0)
        {
            (*Fxn)( a0 );
        }
    };

public:
    delegate() : obj_ptr_( 0 ), stub_ptr_( 0 ) { }

    template<typename T, void (T::*Fxn)(int)>
    void from_function(T * obj)
    {
        obj_ptr_ = const_cast<T const *>( obj );
        stub_ptr_ = &mem_fn_stub<T, Fxn>::invoke;
    }

    template<typename T, void (T::*Fxn)(int) const>
    void from_function(T const * obj)
    {
        obj_ptr_ = obj;
        stub_ptr_ = &mem_fn_const_stub<T, Fxn>::invoke;
    }

    template<void (*Fxn)(int)>
    void from_function()
    {
        obj_ptr_ = 0;
        stub_ptr_ = &function_stub<Fxn>::invoke;
    }

    void operator ()(int a0) const
    {
        ( *stub_ptr_ )( obj_ptr_, a0 );
    }
};

Even though passing a member function as a non-type template parameter is a legitimate C++ feature -- somewhat outdated, but still widely used -- compilers do not support it. The real problem with Sergey's implementation of using the member function as non-type template parameter is not the lack of support from those outdated compilers, but rather its awful syntax. This is due to the fact that non-type template parameters cannot participate in template argument deduction from the function arguments provided. Therefore it must be explicitly specified all the time.

struct foobar
{
    void foo(int) { }
    void bar(int) const { }
};

void hello(int) { }

void main()
{
    foobar fb;
    foobar * pfb = &fb;

    delegate dg;

    dg.from_function<foobar, &foobar::foo>( pfb );
    dg( 1 ); // (pfb->*&foobar::foo)( 1 );

    dg.from_function<foobar const, &foobar::bar>( pfb );
    dg( 1 ); // (pfb->*&foobar::bar)( 1 );

    dg.from_function<&hello>();
    dg( 1 ); // hello( 1 );
}

It is a really bad idea in practice that you should provide template arguments explicitly every time. Usingany_fxn_pointerintroduced previously, we can significantly improve the syntax of the usage and, in turn, the degree of convenience. We just need to add anany_fxn_pointeras a member of the delegate class to store the address of the function of interest.

class delegate
{
    typedef void (*invoke_stub)(void const *, any_fxn_pointer, int);

    void const * obj_ptr_;
    any_fxn_pointer any_;
    invoke_stub stub_ptr_;

    template<typename T, typename TFxn>
    struct mem_fn_stub
    {
        static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0)
        {
            T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) );
            (obj->*reinterpret_cast<TFxn>( any.mfn_ptr ) )( a0 );
        }
    };

    template<typename TFxn>
    struct function_stub
    {
        static void invoke(void const *, any_fxn_pointer any, int a0)
        {
            (*reinterpret_cast<TFxn>( any.fxn_ptr ) )( a0 );
        }
    };

public:
    delegate() : obj_ptr_( 0 ), any(), stub_ptr_( 0 ) { }

    template<typename T>
    void from_function(void (T::*fxn)(int ), T * obj)
    {
        typedef void (T::*TFxn)(int);

        obj_ptr_ = const_cast<T const *>( obj );
        any_.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn );
        stub_ptr_ = &mem_fn_stub<T, TFxn>::invoke;
    }

    template<typename T>
    void from_function(void (T::*fxn)(int) const, T const * obj)
    {
        typedef void (T::*TFxn)(int) const;

        obj_ptr_ = obj;
        any_.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn );
        stub_ptr_ = &mem_fn_stub<T const, TFxn>::invoke;
    }

    void from_function(void (*fxn)(int))
    {
        typedef void (*TFxn)(int);

        obj_ptr_ = 0;
        any_.fxn_ptr = reinterpret_cast<generic_fxn *>( fxn );
        stub_ptr_ = &function_stub<TFxn>::invoke;
    }

    void operator ()(int a0) const
    {
        ( *stub_ptr_ )( obj_ptr_, any_, a0 );
    }
}; // delegate

Not even this works for those outdated compilers that do not support member functions as non-type template parameters. It becomes more intuitive syntax and thus easier to use, since function overloading and automatic template argument deduction is now applicable.

struct foobar
{
    void foo(int) { }
    void bar(int) const { }
};

void hello(int) { }

void main()
{
    foobar fb;
    foobar * pfb = &fb;

    delegate dg;

    dg.from_function( &foobar::foo, pfb );
    dg( 1 ); // (pfb->*&foobar::foo)( 1 );

    dg.from_function( &foobar::bar, pfb );
    dg( 1 ); // (pfb->*&foobar::bar)( 1 );

    dg.from_funcion( &hello );
    dg( 1 ); // hello( 1 );
}

Part C: using function reference tables to support rich features

One of interesting features ofBoost.Functionis its ability to store a member function along with the bound object in various forms: a) a pointer or a reference to the bound object of the type on which the member function call is made, or b) an instance of an arbitrary smart pointer to the bound object. As a matter of fact, it is precise to say that this feature is ofBoost.Mem_fnandBoost.Bind.Boost.Functionhas a 'manager' member and defines enumerate tags of so-called 'functor_manager_operation_type'.

enum functor_manager_operation_type
{
    clone_functor_tag,
    destroy_functor_tag,
    check_functor_type_tag
};

It also defines several tags to distinguish among different types of functions.

struct function_ptr_tag {};
struct function_obj_tag {};
struct member_ptr_tag {};
struct function_obj_ref_tag {};
struct stateless_function_obj_tag {};

This is a traditional tag dispatching technique that uses function overloading to dispatch based on properties of type. This sort of tag dispatching works quite reasonably, but with slightly over complicated construct detail. This is because an implementation for a specific function type is usually scattered here and there. If you have ever tried to look into theBoost.Functiondetail, you know what I mean. However, there exists one very neat and elegant alternative. It was initially introduced by Chris Diggings and Jonathan Turkanis in their series of articles aboutBIL (Boost.Interface.Libarary). We can start off by declaring a function reference table that contains a set of function pointers. These function pointers determine the behavioral requirement or concepts of the delegate that we want to design.

class delegate
{
    typedef void generic_fxn();
    // Function reference table.
    struct fxn_table
    {
        void invoke(void const *, any_fxn_pointer, int);
        void copy_obj(void cons **, void const *);
        void delete_obj(void const *);
        bool is_empty();
    };
 
    void const * obj_ptr_;
    any_fxn_pointer any_;
    fxn_table const * tbl_ptr_;

Then we define class templates that implement all of the entries of function reference table respectively.

  template<typename T, typename TFxn>
  struct mem_fn_stub
  {
      static void init(
          void const ** obj_pptr, any_fxn_pointer & any, T * obj, TFxn fxn)
      {
          *obj_pptr = obj;
          any.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn );
      }

      static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0)
      {
          T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) );
          (obj->*reinterpret_cast<TFxn>( any.mfn_ptr ) )( a0 );
      }

      static void copy_obj(void const ** obj_pptr_dest, void const * obj_ptr_src)
      {
          *obj_pptr_dest = obj_ptr_src; // Simply copies between pointers
      }

      static void delete_obj(void const *)
      {
          // Do nothing.
      }

      static bool is_empty()
      {
          return false;
      }

      static fxn_table const * get_table()
      {
          static fxn_table const static_table = { &invoke, &copy_obj, 
              &delete_obj, &is_empty, };
          return &static_table;
      }
  };

  template<typename T, typename TFxn>
  struct mem_fn_obj_stub
  {
      static void init(
          void const ** obj_pptr, any_fxn_pointer & any, T * obj, TFxn fxn)
      {
          *obj_pptr = new T( *obj );
          any.mfn_ptr = reinterpret_cast<generic_mfn_ptr>( fxn );
      }

      static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0)
      {
          T * obj = static_cast<T *>( const_cast<void *>( obj_ptr ) );
          ( get_pointer(*obj)->*reinterpret_cast<TFxn>( any.mfn_ptr ) )( a0 );
      }

      static void copy_obj(void const ** obj_pptr_dest, void const * obj_ptr_src)
      {
          // Clones the pointed object.
          *obj_pptr_dest = new T( *static_cast<T const *>( obj_ptr_src ) );
      }

      static void delete_obj(void const * obj_ptr)
      {
          // Deletes the pointed object.
          delete static_cast<T const *>( obj_ptr );
      }

      static bool is_empty()
      {
          return false;
      }

      static fxn_table const * get_table()
      {
          static fxn_table const static_table = { &invoke, &copy_obj, 
              &delete_obj, &is_empty, };
          return &static_table;
      }
  };

  template<typename TFxn>
  struct function_stub
  {
      static void init(void const ** obj_pptr, any_fxn_pointer & any, TFxn fxn)
      {
          *obj_pptr = 0;
          any.fxn_ptr = reinterpret_cast<generic_fxn *>( fxn );
      }

      static void invoke(void const *, any_fxn_pointer any, int a0)
      {
          (*reinterpret_cast<TFxn>( any.fxn_ptr ) )( a0 );
      }

      static void copy_obj(void const ** obj_pptr_dest, void const * obj_ptr_src)
      {
          *obj_pptr_dest = obj_ptr_src; // Simply copies between pointers
      }

      static void delete_obj(void const *)
      {
          // Do nothing.
      }

      static bool is_empty()
      {
          return false;
      }

      static fxn_table const * get_table()
      {
          static fxn_table const static_table = { &invoke, &copy_obj, 
              &delete_obj, &is_empty, };
          return &static_table;
      }
  };

We have defined three class templates above. Actually, a class template is not necessary if no extra template parameter is required to implement the behavioral requirement of the specific function type. A normal class will be just fine in such case.

  struct null_stub
  {
      static void invoke(void const *, any_fxn_pointer, int)
      {
          throw bad_function_call();
      }

      static void copy_obj(void const ** obj_pptr_dest, void const *)
      {
          *obj_pptr_dest = 0;
      }

      static void delete_obj(void const *)
      {
          // Do nothing.
      }

      static bool is_empty()
      {
          return true;
      }

      static fxn_table const * get_table()
      {
          static fxn_table const static_table = { &invoke, &copy_obj, 
              &delete_obj, &is_empty, };
          return &static_table;
      }
  };
We can now implement delegate class. We only use entries of the function reference table we declared in the first place through the pointer to the function reference table,tbl_ptr_, to implement member functions of a 'generic' delegate itself.
public:
    delegate()
        : obj_ptr_( 0 ), any_(), tbl_ptr_( null_stub::get_table() ) { }

    delegate(delegate const & other)
        : obj_ptr_( other.obj_ptr_ ), any_( other.any_ ), 
        tbl_ptr_( other.tbl_ptr_ )
    {
        if( other.tbl_ptr_ )
            other.tbl_ptr_->copy_obj( &obj_ptr_, other.obj_ptr_ );
    }

    ~delegate()
    {
        if( tbl_ptr_ )
            tbl_ptr_->delete_obj( obj_ptr_ );
    }

    void swap(delegate & other)
    {
        std::swap( obj_ptr_, other.obj_ptr_ );
        std::swap( any_, other.any_ );
        std::swap( tbl_ptr_, other.tbl_ptr_ );
    }

    bool empty() const
    {
        return tbl_ptr_->is_empty();
    }

    delegate & operator =(delegate const & other)
    {
        if( this != &other )
            delegate( other ).swap( *this );
        return *this;
    }

    void reset()
    {
        delegate().swap( *this );
    }

    void operator ()(int a0) const
    {
        tbl_ptr->invoke( obj_ptr_, any_, a0 );
    }
Defining a class to represent 'null' has several benefits over initializing the pointer to the function reference table,tbl_ptr_, to a zero value. It is not necessary to check nullness of delegate at runtime. If the pointer to the function reference table were to be assigned to zero to represent null delegate instead, the function call operator should have contained an if-clause to check the nullness. Most of all, it should also have had an exception statement to throw when the call had been made on the empty delegate, which seemed quite an unreasonable penalty.

Since nullness of delegate is determined at compile time by introducing 'null' class,null_stub, we can improve the performance by not checking the nullness at runtime and by not having throw statement if it is not a 'null' delegate. Finally, we can complete our new delegate class by adding interface member functions to support storing from various function types.

  template<typename T, typename U>
  void from_function(void (U::*fxn)(int), T obj)
  {
      reset();

      typedef void (U::*TFxn)(int);

      struct select_stub
      {
          typedef typename boost::ct_if<
              (::boost::is_pointer<T>::value),
              mem_fn_stub<typename boost::remove_pointer<T>::type, TFxn>,
              mem_fn_obj_stub<T, TFxn>
              > type;
      };

      select_stub::type::init( &obj_ptr_, any_, obj, fxn );
      tbl_ptr_ = select_stub::type::get_table();
  }

  template<typename T, typename U>
  void from_function(void (U::*fxn)(int) const, T obj)
  {
      reset();

      typedef void (U::*TFxn)(int) const;

      struct select_stub
      {
          typedef typename boost::ct_if<
              (::boost::is_pointer<T>::value),
              mem_fn_stub<typename boost::remove_pointer<T>::type, TFxn>,
              mem_fn_obj_stub<T, TFxn>
              > type;
      };

      select_stub::type::init( &obj_ptr_, any_, obj, fxn );
      tbl_ptr_ = select_stub::type::get_table();
  }

  void from_function(void (*fxn)(int))
  {
      reset();

      typedef void (*TFxn)(int);

      function_stub<TFxn>::init( &obj_ptr_, any_, fxn );
      tbl_ptr_ = function_stub<TFxn>::get_table();
  }

}; // class delegate

We just added smart pointer support into our delegate. We can now use any kind of smart pointer to bind the target object on which the member function call is made, as long as theget_pointer()overload for the smart pointer is provided in the visible namespace scope.

struct foobar
{
    void foo(int) { }
    void bar(int) const { }
};

template<typename T>
T * get_pointer(boost::shared_ptr<T> const & t)
{
    return t.get();
}

void main()
{
    boost::shared_ptr<foobar> spfb(new foobar);

    delegate dg;

    dg.from_function( &foobar::foo, spfb );
    dg( 1 ); // (get_pointer(spfb)->*&foobar::foo)( 1 );
}

As demonstrated above, it is quite simple to add new function type support into our delegate. First, it determines all the behavioral requirements according to the characteristic of the function type that we are interested in storing. Then it adds a class template and starts defining all the entries of function reference table for the new function type per requirements. Finally, it adds an interface function overload --from_function()member function in the example above -- to support the newly added function type.

If we want to add more behavioral requirements or concepts into the delegate, we can add new entries into the function reference table to fulfill the purpose. Of course, all of the existing class templates already defined for some other function types must implement newly added entries accordingly. Otherwise, it will assert a compile error. Be aware that even if these newly added entries might not apply for some of class templates already defined for certain function types, they still have to define empty entries, which may do nothing and thus possibly be optimized away by the compiler.

As easily seen from the example above, all entries in a function reference table are declared in a type neutral form. This means that it may be determined by the function call type signature, but nothing else. Also, we can see that void pointers are passed over as their arguments, and the specific type information is given as the template parameters of the class template that implements all of the entries of the function reference table. Those void pointers passed in and out come alive and restore themselves back to their original identities by the aid of casting to one of the template parameters of class template. However, it will assuredly erase the type information only during its storage life span and convert back to the right identity when required. This is the key point of how the different set of behavioral requirements of a delegate for a specific function type is implemented in the type-safe manner, but with type-neutral persistency.

Implementation details are to be defined in one location, in a class template for a specific function type, so it has better readability and manageability than the tag dispatching technique. It is like a 'strategy pattern' to make it possible to encapsulate the interchangeable algorithm of delegate for various function types. Actually, a function reference table is an interface and the technique can be thought of as a static typing or static binding version of the virtual table feature in C++.

Every individual class template that implements all of the entries of a function reference table according to their own requirements for the specific function type are orthogonal to each other. This means that we don't need to concern ourselves with how a function object is stored into the delegate while designing a class template to store a member function, and vice versa. So what does this mean? I can now implement and support a multicast delegate much easier in one single delegate class definition. There will be absolutely no degradation in runtime performance nor will there be a size increase of non-multicast delegate. This is because they -- that is, the multicast delegate and non-multicast delegate -- are treated as completely different beasts of the same name.

Therefore it becomes very important how robustly we declare entries of a function reference table to generalize the behavioral requirements of the generic delegate that we are interested in storing from the given function type, as well as how to categorize these function types based on their own unique traits and requirements.

Selecting a proper function reference table for the specific function type takes place in the implementation of the interface function overloads of delegate class. It usually involves template meta programming along with type traits to determine or to transform from a function type. Unfortunately, some type traits I used while implementing the selection structure in the interface function overloads only work in places based on the template partial specialization feature of C++ (boost::remove_pointer<>for example). This is whyFD.Delegatedoes not work on some outdated compilers that have no support or broken support for this feature. If I were to support only a limited feature set, like Don's fastest delegate or Sergey's fast delegate, I could have supported many outdated compilers as well. However, I wanted it to be a 'drop-in' replacement forBoost.Function, so this was not an option for me.

D. Categorizing function types.

There are three dominant callable entities in the C++ world.

  1. Free function
  2. Member function
  3. Function object

These basic callable entities are strained off more refined species depending on how their bound object or function object is stored and managed internally. See the table below.

Function type Category Description FT01. FT02. FT03. FT04. FT05. FT06. FT07. FT08. FT09. FT10. FT11.
1. Free function (including static member function)
2-a. Member function on a pointer to an object of the type that the member function belongs to.
2-b. Member function on a reference to an object of the type that the member function belongs to.
2-c. Member function on a copy of an object of either the type that the member function belongs to or any type that supportsget_pointer()overload (i.e. smart pointers).
2-d. Member function bound with a pointer or a reference to an object of the type that the member function belongs to.
2-e. Member function bound with a copy of an object of any type that supportsget_pointer()overload (i.e. smart pointers).
3-a. A pointer or a reference to a function object.
3-b. A copy of a function object.
3-c. Stateless function object.
Empty delegate.
Multicast delegate.

In the previous delegate example, we added supports for function type FT01, FT05, FT06 and FT10. To make it easier to understand what these function types are, see the example as illustrated below.

struct foobar
{
    int id_;

    void foo(int) { }
    static void bar(int) { }

    void operator ()(int) const { }
};

void hello(int) { }

struct stateless
{
    void operator ()(int) const { }
};

void main()
{
    delegate<void (int)> dg1;

    foobar fb;
    foobar * pfb = &fb; 
    boost::shared_ptr<foobar> spfb( new foobar );

    dg1 = &hello;                               // FT01
    dg1( 1 );                                   // hello( 1 );

    dg1 = &foobar::bar;                         // FT01
    dg1( 1 );                                   // foobar::bar( 1 );

    delegate<void (foobar *, int)> dg2;
    dg2 = &foobar::foo;                         // FT02
    dg2( pfb, 1 );                              // (pfb->*&foobar::foo)( 1 );

    delegate<void (foobar &, int)> dg3;
    dg3 = &foobar::foo;                         // FT03
    dg3( fb, 1 );                               // (fb.*&foobar::foo)( 1 );

    delegate<void (foobar, int)> dg4;
    dg4 = &foobar::foo;                         // FT04
    dg4( fb, 1 );                       // ((copy of fb).*&foobar::foo)( 1 );

    delegate<void (boost::shared_ptr<foobar>, int)> dg5;

    dg5 = &foobar::foo;                         // FT04
    dg5( spfb, 1 );                 // (get_pointer(spfb)->*&foobar::foo)( 1 );

    dg1.bind( &foobar::foo, pfb );              // FT05
    dg1( 1 );                                   // (pfb->*&foobar::foo)( 1 );

    dg1.bind( &foobar::foo, boost::ref( fb ) ); // FT05
    dg1( 1 );                                   // (fb.*&foobar::foo)( 1 );

    dg1.bind( &foobar::foo, spfb );             // FT06
    dg1( 1 );                       // (get_pointer(spfb)->*&foobar::foo)( 1 );

    dg1 = pfb;                                  // FT07
    dg1( 1 );                                   // (*pfb)( 1 );

    dg1 = boost::ref( fb );                     // FT07
    dg1( 1 );                                   // fb( 1 );

    dg1 = fb;                                   // FT08
    dg1( 1 );                                   // (copy of fb)( 1 );

    dg1 = stateless();                          // FT09
    dg1( 1 );                                   // stateless()( 1 );

    dg1 = 0;                                    // FT10
    try { dg1( 1 ); }                           // throw bad_function_call();
    catch(bad_function_call) { }

    dg1 += &hello;                              // FT11
    dg1 += delegate<void (int)>( &foobar::foo, spfb );
    dg1 += fb;

    dg1( 1 );                                   // hello( 1 );
                                  // (get_pointer(spfb)->*&foobar::foo)( 1 );
                                              // (copy of fb)( 1 );
}

Part E: list of entries of function reference table

As emphasized previously, it is very important to declare common and representative entries of the function reference table for a generic delegate. It is the basis of the wholeFD.Delegate's design and determines performance, as well as robustness. The following list of function entries are declared in order to generalize the common behavioral requirements of a generic delegate for the various function types that we categorized above.

Invocation

  1. invoke() - Invokes the underlying callable entity.

Object management

  1. copy_obj() - Copies the object to destination from source.
  2. delete_obj() - Deletes the object.

General information inquiry

  1. is_empty() - Determines whether or not the delegate is empty.
  2. size_of_fxn() - Retrieves size of the underlying callable entity.
  3. type_of_fxn() - Retrieves std::type_info of the underlying callable entity.
  4. is_functor() - Determines whether or not the underlying callable entity is a function object.

Comparisons

  1. compare_equal_same_type() - Compares equality of two underlying callable entities of the same type.
  2. memcmp_delegate() - Non contextual memory comparison between underlying callable entities of two arbitrary types.

Multicast

  1. is_multicast() - Determines whether or not the delegate is a multicast.
  2. add_delegates() - Adds one or more delegates into multicast.
  3. remove_delegates() - Removes one or more delegates from multicast.
  4. find_delegate() - Determines whether or not the specified delegate is in multicast.

Part F: generic data structure to store a callable entity

It is required to have at least two void pointers and anany_fxn_pointerto store all the previously categorized function types into a generic delegate. To make it easy to access and manipulate these void pointers, as well asany_fxn_pointer, they are bundled up together into a single structure called 'delegate_holder'.

struct delegate_holder
{
    void const * obj_ptr;
    any_fxn_pointer any;
    void const * tbl_ptr;
};

See the table below to understand how these void pointer members are used effectively in a situation of storing one of the various function types.

Function type delegate_holder Fast delegate (Yes / No)
Equality comparison with self type (compare_eq_same_type)
.obj_ptr .any .tbl_ptr FT01 Not used Function Pointer Pointer to function reference table Yes Compare any.fxn_ptr FT02 Not used Member function pointer Pointer to function reference table Yes Compare any.mfn_ptr FT03 Not used Member function pointer Pointer to function reference table Yes Compare any.mfn_ptr FT04 Not used Member function pointer Pointer to function reference table Yes Compare any.mfn_ptr FT05 Pointer to bound object Member function pointer Pointer to function reference table Yes Compare any.mfn_ptr, obj_ptr and *obj_ptr in order FT06 Pointer to heap allocated bound object Member function pointer Pointer to function reference table No Compare any.mfn_ptr, get_pointer(*obj_ptr) and *get_pointer(*obj_ptr) in order FT07 Pointer to function object Not used Pointer to function reference table Yes Compare obj_ptr and *obj_ptr in order FT08 Pointer to heap allocated function object Not used Pointer to function reference table No Compare *obj_ptr FT09 Not used Not used Pointer to function reference table Yes TRUE always FT10 Not used Not used Pointer to function reference table Yes TRUE always FT11 Pointer to heap allocated delegate_holder list Not used Pointer to function reference table No for_each (list of delegate_holder) compare_eq_same_type one by one

Part G: supporting multicast

In order to support multicast, it is important to model theEqualityComparableconcept to be able to compare between two delegates of the same type. As explained in theFrequently Asked QuestionsofBoost.Function, it is a well-known fact that there is no reasonable method at the moment to distinguish whether or not the specific function object has an accessible equality comparison operator defined. Because of theBoost.FunctionandFD.Delegateerasing of type information and restoring it back when required, it will assert a compile error. This is regardless of whether the comparison between two delegates of an arbitrary function object type has actually been performed or not. This is also the case if it is forced to use the equality comparison operator when the equality comparison operator is not available or not accessible for the specified function object type.

Obviously, it is not acceptable to cause a compile error when we do not compare delegates. So,FD.Delegatewas made to returnfalseby default and it does not use the equality comparison operator when comparing between two delegates of the same type that store a function object as their underlying callable entity. However, it can be altered to return the result of the equality comparison operator of the specific function object type, thus possiblytrue, if a certain condition is met. This will be explained later. By the way, there is no issue of comparing between two delegates of the same type when their underlying callable entity is either a free function or a member function, since function pointers of the same type are equality comparable in C++.

As mentioned previously, it was possible to compare between two delegates of the same function object type using the equality comparison operator, instead of returningfalseblindly, if a certain condition is met. The user can manually indicate that the specific function object type has an accessible equality comparison operator by defining a template specialization offd::is_equality_comparable<>for the function object type or possibly by using the easier macro definition provided for this purpose,FD_DELEGATE_EQUALITY_COMPARABLE_TYPE.FD.Delegatewill use the equality comparison operator and will return the result of the comparison according to the implementation of the operator.

struct make_int
{
    make_int(int n, int cn) : N(n), CN(cn) {}

    int operator()() { return N; }
    int operator()() const { return CN; }

    int N;
    int CN;
};

bool operator ==(make_int const & lhs, make_int const & rhs)
{
    return lhs.N == rhs.N;
}

FD_DELEGATE_EQUALITY_COMPARABLE_TYPE(make_int); // (A)

void main()
{
    delegate0<void> dg1, dg2;

    dg1 = make_int( 1, 10 );
    dg2 = make_int( 1, 20 );

    assert( dg1.equal_to( dg2 ) == true ); // (B)
}

If line (A) is commented out, the assert statement (B) will always holdfalseand never becometrue, as explained previously. Also note that we do not use the equality comparison operator forFD.Delegateto compare between the two. This is because it is not a valid expression inBoost.Functionfor same reasons as explained above. Furthermore,FD.Delegateis aBoost.Function'drop-in' replacement. So instead, we use theequal_to()member function for an equality comparison between two delegates. The following code snippet is a pseudo-code illustration of how comparison between two delegates is performed in order.

// Exposition purpose only.
bool delegate::equal_to(delegate const & other)
{
    if( this->type_of_fxn() != other.type_of_fxn() )
    then return false

    if( this->get_holder().any.mfn_ptr != other.get_holder().any.mfn_ptr )
    then return false;

    if( this->get_holder().obj_ptr == other.get_holder().obj_ptr )
    then return true;

    return this->compare_equal_same_type( this->get_holder(), 
      other.get_holder() );
}

compare_equal_same_type()is one of entries of the function reference table explained previously. So, a class template for a different function type can implement it differently according to its own requirement. All class templates whose designated function type requires the equality comparison between two of the same function object type will implement such a comparison to returnfalse. Otherwise,fd::is_equality_comparable<>specialization for the specified function object type is defined to indicate the availability of the equality comparison operator.

Unfortunately, even such limited support for the equality comparison is not applicable for comparing between two delegates of the same type for an anonymous function object that we frequently face in everyday STL programming. It is not impossible, but it is impractical to definefd::is_equality_comparable<>specialization for an anonymous function object.Boost.Signalssolves this problem by providing theconnect()method to return an object called a 'connection'. The 'connection' object can be used either to disconnect the connection made or to check the validity of the connection. The same idea goes withFD.Delegate, but with a slightly different name tag.FD.Delegatehas a member function calledadd(), which is almost equivalent tooperator +=, but they are different in their return type.

operator +=andoperator -=return a self-reference, whileadd()returns an object called a 'token' andremove()returns the number of delegates in the multicast delegate. 'token' is very similar to what is 'connection' forBoost.Signals. It can be used either to remove one or more delegates added before or to check the validity of the 'token' itself.

void main()
{
    delegate1<int, int> dg1;

    dg1 += std::negate<int>();

    token tk1 = dg1.add( std::bind1st( std::plus<int>(), 3 ) ); // (A)
    assert( tk1.valid() == true );

    assert( dg1.count() == 2 );

    dg1.remove( std::bind1st( std::plus<int>(), 3 ) ); 
        // Can not remove the delegate added in the 
        // line (A) std::bind1st( std::plus<int>(), 3 )

    assert( dg1.count() == 2 );

    tk1.remove(); 
        // Removes the delegate added in the line 
        // (A) std::bind1st( std::plus<int>(), 3 )

    assert( dg1.count() == 1 );
}

Part H: combiner interface

What is missing in C# delegate is multiple return values management of multicast invocation.Boost.Signalsemployes a technique, called combiner interface, to serve two purposes effectively: a) multiple return values management; b) multiple invocations control. See theBoost.Signalsdocumentation for thecombiner interface.

The idea and most of the implementation details ofBoost.Signals's combiner interface are copied and incorporated intoFD.Delegate. However, there is one big difference in their usage as multicast delegates. In order to be coincident withBoost.Function, the combiner interface ofFD.Delegateis not designed to be a part of the delegate class type as a form of template parameter the way it is forBoost.Signals. Instead,FD.Delegateintroduces a special function call operator that takes an instance of the combiner interface as the last function call argument. See the example below.

struct maximum
{
    typedef int result_type;

    template<typename InputIterator>
    int operator()(InputIterator first, InputIterator last) const
    {
        if(first == last)
        return 0;

    int max = *first++;
    for(; first != last; ++first)
      max = (*first > max)? *first : max;

    return max;
  }
};

void main()
{
    delegate2<int, int, int> dg1;

    dg1 += std::plus<int>();
    dg1 += std::multiplies<int>();
    dg1 += std::minus<int>();
    dg1 += std::divides<int>();

    int max = dg1( 5, 3, maximum() );
    assert( max == 15 );
}

While a combiner interface is used to manage multiple return values of multicast invocation in most cases, it can be also used to control the multiple invocation itself viamulticast_call_iterator, which is copied and modified fromBoost.Signals'sslot_call_iterator. See the example below. It is possible to abort all invocations of delegates in the multicast or to skip certain invocations of delegates when the combiner interface provided is implemented to require such operational conditions.

template<typename T>
struct first_positive 
{
    typedef T result_type;

    template<typename InputIterator>
    T operator()(InputIterator first, InputIterator last) const
    {
        while (first != last && !(*first > 0)) 
            // Aborts if the result is the first positive.
        {
            ++first;
        }
    return (first == last) ? 0 : *first;
  }
};

template<typename T>
struct noisy_divide 
{
    typedef T result_type;

    T operator()(T const & x, T const & y) const
    {
        std::cout << "Dividing " << x << " and " << y << std::endl;
        return x/y;
    }
};

int main()
{
    fd::delegate2<int, int, int> dg_positive;

    dg_positive += std::plus<int>();
    dg_positive += std::multiplies<int>();
    dg_positive += std::minus<int>();
    dg_positive += noisy_divide<int>();

    assert(dg_positive(3, -5, first_positive<int>()) == 8); 
        // returns 8, but prints nothing.

    return 0;
}

Any combiner interface that is implemented forBoost.Signalswill work forFD.Delegatewithout a modification. It is obvious thatFD.Delegatesupports many useful features ofBoost.Signalswhen it operates as a multicast delegate. However, it is never meant to replaceBoost.Signals. Some very sophisticated features ofBoost.Signalsare not implemented inFD.Delegate, but it can still serve as more than a generic multicast delegate while it stands for a 'drop-in' replacement ofBoost.Function.

Delegates comparison chart

Feature Description Don.FD Sergey.FD Jae.FD Boost.Function Boost.Signals Free function (including static member function) / FT01 Member function on a pointer to object of the type which the member function belongs to / FT02 Member function on a reference to object of the type which the member function belongs to / FT03 Member function on a copy of an object of either the type which the member function belongs to or any type which supportsget_pointer()overload (i.e. smart pointers) / FT04 Member function bound with a pointer to object of the type which the member fucntion belongs to / FT05 Member function bound with a copy of an object of any type which supportsget_pointer()overload (i.e. smart pointers) / FT06 A pointer to a function object / FT07 A copy of an object of function object type / FT08 Stateless function object / FT09 Empty delegate throw bad_function_call exception when invoked / FT10 Multicast delegate / FT11 Combiner interface to manage multiple returns of multicast Combiner interface to control invocation of multicast ( xxx_call_iterator ) Connection management (multicast) Ordering slot call group (multicast) Named slot (multicast) Trackable support (multicast) Relaxed function type signature Fast Delegate for member function call Size of object (32 bit system) Size of object when member function is stored (32 bit system) Copy speed when member function is stored Invocation speed Equality comparable for self type Equality comparable to an arbitrary function type (not self type) Less than comparable Custom allocator Calling conventions ( __stdcall, __fastcall, __cdecl ) Boost.Functiondrop-in replacement C++ standard compliant Portability Boost Dependency
Yes Yes Yes Yes Yes
No No Yes Yes Yes
No No Yes Yes Yes
No No Yes Yes Yes
Yes Yes Yes Yes Yes
No No Yes Yes Yes
No No Yes Yes Yes
No No Yes Yes Yes
No No Yes Yes Yes
Possible Possible Yes Yes N/A
No No Yes No Yes
N/A N/A Yes N/A Yes
N/A N/A Yes N/A Yes
N/A N/A Yes N/A Yes
N/A N/A No N/A Yes
N/A N/A No N/A Yes
N/A N/A No N/A Yes
No No Yes Yes Yes
Yes Yes Yes No No
8 or 12 bytes 8 bytes 16 bytes + α 12 bytes +α 36 bytes +α
8 or 12 bytes 8 bytes 16 bytes 12 bytes +α 36 bytes +α
●●●●● ●●●●● ●●●●○ ●○○○○ ●○○○○
●●●●● ●●●●○ ●●●●○ ●●●○○ ●●●○○
Yes No Support No No
No No Yes Yes No
Yes No Yes No No
N/A N/A Yes Yes Yes
No No Yes Yes Yes
No No Yes Yes No
No Yes Yes Yes Yes
●●●●● ●●○○○ ●●●●○ ●●●●● ●●●●●
No No Yes Yes Yes

Helper function template and class template

FD.Bind

FD.Bindis a set of helper function overloads that return an instance ofFD.Delegate. UnlikeBoost.Bind,FD.Bindcan only be used to bind the object on which the member function call is made with the member function.FD.Bindcan return aFD.Delegateinstance of either function type FT05 or FT06, where FT05 is a fast delegate function type. Of course,FD.Delegatewill work withBoost.Bindflawlessly as well.

#include <fd/delegate.hpp>
#include <fd/delegate/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/ref.hpp>

struct foobar
{
    void foo(int) { }
};

void main()
{
    fd::delegate<void (int)> dg1;

    foobar fb;
    foobar * pfb = &fb;
    boost::shared_ptr<foobar> spfb( new foobar );

    dg1.bind( &foobar::foo, pfb );                    // FT05
    dg1 = fd::bind( &foobar::foo, pfb );              // FT05
    dg1( 1 );                                   // (pfb->*&foobar::foo)( 1 );

    dg1.bind( &foobar::foo, boost::ref( fb ) );       // FT05
    dg1 = fd::bind( &foobar::foo, boost::ref( fb ) ); // FT05
    dg1( 1 );                                   // (fb.*&foobar::foo)( 1 );

    dg1.bind( &foobar::foo, fb );                     // FT06
    dg1 = fd::bind( &foobar::foo, fb );               // FT06
    dg1( 1 );                         // ((copy of fb).*&foobar::foo)( 1 );

    dg1.bind( &foobar::foo, spfb );                   // FT06
    dg1 = fd::bind( &foobar::foo, spfb );             // FT06
    dg1( 1 );                         // (get_pointer(spfb)->*&foobar::foo)( 1 );
}

FD.Resolution

When support for the relaxed function type signature is implemented, the library become more useful. However, one issue has emerged as the price of convenience. See the example below.
struct foobar
{
    long hello(long) { return 0; }

    int  foo(int)    { return 0; } // (A)
    long foo(long)   { return 0; } // (B)

    int  bar(int)    { return 0; } // (C)
    int  bar(long)   { return 0; } // (D)
};

void main()
{
    boost::function<int (foobar *, int)> fn;

    fn = &foobar::hello; // Compile Okay, relaxed function type signature.
                       // Implicitly convertible from 'int' to 'long'.

    fn = &foobar::foo;   // Ambiguity due to the support for the 
                       // relaxed function type signature.
                       // (A) or (B) ?

    my_delegate_do_not_support_relaxed<int (foobar *, int)> md;

    md = &foobar::hello; // Compile error.

    md = &foobar::foo;   // No problem, choose (A).
}

Many libraries fromBoostas well asFD.Delegatesuffer from the ambiguity issue illustrated above. They usually support minimal but incomplete or inconvenient devices to remedy the issue. You can explicitly specify the return or argument type to help overload resolution by designating those types in the function call syntax.

void main()
{
    foobar fb;
    foobar * pfb = &fb;

    boost::bind<int>( &foobar::foo, pfb, _1 );  // (A)
    boost::bind<long>( &foobar::foo, pfb, _1 ); // (B)

    // boost::bind<int, ???>( &foobar::bar, pfb, _1 ); 
    // Can't solve the ambiguity.

    boost::mem_fn<int>( &foobar::foo );  // (A)
    boost::mem_fn<long>( &foobar::foo ); // (B)

    // boost::mem_fn<int, ???>( &foobar::bar ); // Can't solve the ambiguity.

    boost::function<int (int)> fn;

    fd::bind<int>( &foobar::foo, pfb );  // (A)
    fd::bind<long>( &foobar::foo, pfb ); // (B)

    fd::bind<int, int>( &foobar::bar, pfb );  // (C)
    fd::bind<int, long>( &foobar::bar, pfb ); // (D)

    fd::delegate<int (int)> dg;
    dg = fd::bind<int, int>( &foobar::bar, pfb );  // (C)
    dg = fd::bind<int, long>( &foobar::bar, pfb ); // (D)

    dg.bind<int, int>( &foobar::bar, pfb );  // (C)
    dg.bind<int, long>( &foobar::bar, pfb ); // (D)
}

FD.Resolutionis a tiny utility class template that comes in handy in such situations to resolve overload resolution ambiguity. It is much more generic and intuitive because you don't need to look into the individual library details to figure out the order of the template parameters in order to specify the argument types in the right placement. Notice thatBoost.BindandBoost.Mem_fnonly allow you to specify the return type to help function overload resolution. Also note thatFD.Resolutionis an independent utility class and thus can be used withoutFD.Delegate.

#include <fd/resolution.hpp>

using fd::resolution;

void main()
{
    foobar fb;
    foobar * pfb = &fb;

    boost::bind( resolution<int (int)>::select( &foobar::foo ), pfb, _1 );   
        // (A)
    boost::bind( resolution<long (long)>::select( &foobar::foo ), pfb, _1 ); 
        // (B)

    boost::bind( resolution<int (int)>::select( &foobar::bar ), pfb, _1 );   
       // (C)
    boost::bind( resolution<int (long)>::select( &foobar::bar ), pfb, _1 );  
       // (D)

    boost::mem_fn( resolution<int (int)>::select( &foobar::foo ) );   // (A)
    boost::mem_fn( resolution<long (long)>::select( &foobar::foo ) ); // (B)

    boost::mem_fn( resolution<int (int)>::select( &foobar::bar ) );  // (C)
    boost::mem_fn( resolution<int (long)>::select( &foobar::bar ) ); // (D)

    boost::function<int (int)> fn;
    fn = boost::bind( resolution<int (int)>::select( &foobar::bar ), pfb, _1 );  
        // (C)
    fn = boost::bind( resolution<int (long)>::select( &foobar::bar ), pfb, _1 );
        // (D)

    fd::bind( resolution<int (int)>::select( &foobar::foo ), pfb );   // (A)
    fd::bind( resolution<long (long)>::select( &foobar::foo ), pfb ); // (B)

    fd::bind( resolution<int (int)>::select( &foobar::bar ), pfb );  // (C)
    fd::bind( resolution<int (long)>::select( &foobar::bar ), pfb ); // (D)

    fd::delegate<int (int)> dg;
    dg = fd::bind( resolution<int (int)>::select( &foobar::bar ), pfb );  // (C)
    dg = fd::bind( resolution<int (long)>::select( &foobar::bar ), pfb ); // (D)

    dg.bind( resolution<int (int)>::select( &foobar::bar ), pfb );  // (C)
    dg.bind( resolution<int (long)>::select( &foobar::bar ), pfb ); // (D)
}

Regression test

See the '%FD_ROOT%/libs/delegate/test' for details.FD.Delegatehas been built based onBoost 1.33.1. As mentioned,FD.Delegateuses several type trait classes fromBoostto transform type information. These features, especiallyremove_xxxs, require compiler support for the partial template specialization. VC6 and VC7 do not support or have broken support for the partial template specialization. However, there are some.

Test Name Test
Type Intel C++
8.1 WIN32 Intel C++
9.1 WIN32 MinGW 3.4.2 GNU gcc
3.4.43 MSVC++
6sp5 MSVC++
6sp5#2(3) MSVC++
2003sp1 MSVC++
2005sp1b bind_cdecl
_mf_test bind_eq
_test bind
_fastcall
_mf_test bind
_function
_test bind
_stdcall
_mf_test mem_fn
_cdecl_test mem_fn
_derived
_test mem_fn
_eq_test mem_fn
_fastcall
_test mem_fn
_stdcall
_test mem_fn
_test mem_fn
_void_test empty
_delegate allocator
_test contains2
_test contains
_test delegate
_30 delegate
_arith
_cxx98 delegate
_arith
_portable delegate
_n_test delegate
_ref
_cxx98 delegate
_ref
_portable delegate
_test delegate
_test
_fail1 delegate
_test
_fail2 lambda
_test mem_fun
_cxx98 mem_fun
_portable stateless
_test std_bind
_cxx98 std_bind
_portable sum_avg
_cxx98 sum_avg
_portable function
_type get
_pointer multicast
_and
_empty multicast
_call
_iterator multiple
_inheritance resolution
_select
_cxx98 resolution
_select
_portable deletion
_test signal
_n_test signal_test type_info virtual
_inheritance
run Fail(1) Fail(1) Fail(2) Fail(2) Fail Pass Pass Pass
run Pass Pass Pass Pass Fail Pass Pass Pass
run Pass Pass Fail(2) Fail(2) Fail Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Fail(2) Fail(2) Fail Pass Pass Pass
run Fail(1) Fail(1) Fail(2) Fail(2) Pass Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Fail(2) Fail(2) Pass Pass Pass Pass
run Pass Pass Fail(2) Fail(2) Pass Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
compile Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
compile
_fail
Pass Pass Pass Pass Pass Pass Pass Pass
compile
_fail
Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Pass Pass Pass Pass
run Pass Pass Pass Pass Fail Fail Pass Pass
run Pass Pass Pass Pass Fail Pass Fail(4) Fail(4)
run Pass Pass Pass Pass Pass Pass Pass Pass

(1) Non-specified calling convention is treated exactly the same as__cdeclin Intel C++ compiler for WIN32. Therefore do not define both (non-specified and__cdecl) at the same time.

References

  • [Boost].Boost.Function,Boost.Bind,Boost.Mem_fnandBoost.Signals
  • [Sergey.FD]. "The Impossibly Fast C++ Delegates" by Sergey Ryazanov
  • [Don.FD]. "Member Function Pointers and the Fastest Possible C++ Delegates" by Don Clugston.
  • [BIL]. "C++ Boost Interface Library ( BIL )" by by Jonathan Turkanis and Christopher Diggins.

History

  • April 12 2007 - v1.00
    • Initial Release.
  • April 13 2007 - v1.01
    • ARTICLE:Boost.Function'sany_pointerdoes not use the union trickery for the same reason.
    • ARTICLE: Uses a generic function pointer,generic_fxn *, to mark functions instead ofvoid *.
    • CODE: Used the pre-defined (typedef void generic_fxn();) generic function pointer to mark functions intead ofvoid *. This change has been made fortarget()member function.
  • April 18 2007 - v1.02
    • ARTICLE: Assigning an arbitrary smart pointer of function object type (FT08) is not supported anymore.
    • ARTICLE: Added regression test results for VC6 withBoost 1.33.1andBoost 1.34 alpha.
    • CODE: Changed the way to applyget_pointer()to help VC6 for the overload resolution. Unfortunately, as a result, FT08 smart pointer type can not be used anymore.
    • CODE: Incorporated and modifiedBoost'sget_function_tag<>to help VC6 for the overload resolution.
    • CODE: Added Several workarouns for VC6 specific.
    • CODE: Revised several test samples into portable syntax to make them work for VC6.
  • April 30 2007 - v1.10
    • ARTICLE: Incorrect information regarding to the union trickery has been completely removed.
    • ARTICLE: Added explanation about the new approach to store member function pointer depending on its size.any_fxn_pointer.
    • CODE: Removed incorrect union trickery that was used for marking member function pointer.
    • CODE: Addedany_fxn_pointerimplementation to substitute the removed union trickery.
    • CODE: Added two test cases to check storing member function pointer to multiple inherited class andvirtualinherited class.
    • CODE: Addedsimplify_mfnstruct simliar to Don'sSimplifyMemFuncto help MSVC which can not cast a member function pointer of unrelated classes even though it is required according to C++ standards.
  • June 1, 2007 - Article edited and moved to the main CodeProject.com article base

(2)gccsupports calling conventions, but does not effectively resolve the overload ambiguity among them.
(3)FD.Delegatehas been built based onremove_xxxtype traitsfromBoost 1.34 pre alphainstead ofBoost 1.33.1since the new version incorporatesworkaroundsfor VC6, VC7 and VC7.1 specific.

(4) VC71 & VC8bug.
unds for VC6, VC7 and VC7.1 specific.
(4) VC71 & VC8bug.

References

  • [Boost].Boost.Function,Boost.Bind,Boost.Mem_fnandBoost.Signals
  • [Sergey.FD]. "The Impossibly Fast C++ Delegates" by Sergey Ryazanov
  • [Don.FD]. "Member Function Pointers and the Fastest Possible C++ Delegates" by Don Clugston.
  • [BIL]. "C++ Boost Interface Library ( BIL )" by by Jonathan Turkanis and Christopher Diggins.

History

  • April 12 2007 - v1.00
    • Initial Release.
  • April 13 2007 - v1.01
    • ARTICLE:Boost.Function'sany_pointerdoes not use the union trickery for the same reason.
    • ARTICLE: Uses a generic function pointer,generic_fxn *, to mark functions instead ofvoid *.
    • CODE: Used the pre-defined (typedef void generic_fxn();) generic function pointer to mark functions intead ofvoid *. This change has been made fortarget()member function.
  • April 18 2007 - v1.02
    • ARTICLE: Assigning an arbitrary smart pointer of function object type (FT08) is not supported anymore.
    • ARTICLE: Added regression test results for VC6 withBoost 1.33.1andBoost 1.34 alpha.
    • CODE: Changed the way to applyget_pointer()to help VC6 for the overload resolution. Unfortunately, as a result, FT08 smart pointer type can not be used anymore.
    • CODE: Incorporated and modifiedBoost'sget_function_tag<>to help VC6 for the overload resolution.
    • CODE: Added Several workarouns for VC6 specific.
    • CODE: Revised several test samples into portable syntax to make them work for VC6.
  • April 30 2007 - v1.10
    • ARTICLE: Incorrect information regarding to the union trickery has been completely removed.
    • ARTICLE: Added explanation about the new approach to store member function pointer depending on its size.any_fxn_pointer.
    • CODE: Removed incorrect union trickery that was used for marking member function pointer.
    • CODE: Addedany_fxn_pointerimplementation to substitute the removed union trickery.
    • CODE: Added two test cases to check storing member function pointer to multiple inherited class andvirtualinherited class.
    • CODE: Addedsimplify_mfnstruct simliar to Don'sSimplifyMemFuncto help MSVC which can not cast a member function pointer of unrelated classes even though it is required according to C++ standards.
  • June 1, 2007 - Article edited and moved to the main CodeProject.com article base