Click here to Skip to main content
Click here to Skip to main content

Fast C++ Delegate: Boost.Function 'drop-in' replacement and multicast

, 1 Jun 2007
Rate this:
Please Sign up or sign in to vote.
An article on the implementation of a fast C++ delegate with many advanced features.

Introduction

There have been several C++ delegates which declared themselves as a 'fast' or 'fastest' delegate, while Boost.Function and its siblings, Boost.Bind and Boost.Mem_fn, were adopted as part of C++ Standards Committee's Library Technical Report (TR1). So, what are those called 'fast' or 'fastest' delegates and how much 'faster' are they than Boost.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.Function is 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 and Boost.Function in 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 and Boost.Function. For a bound member function call, it was found that Boost.Function can 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 of Boost.Function is 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 as those which Boost.Function and its siblings provide, and we are already accustomed to using Boosts. 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 a Boost.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.Delegate can be thought of as an aggregation of Boost.Function and its siblings (Boost.Bind and Boost.Mem_fn) plus some features from Boost.Signals. See the 'Delegates Comparison Chart' at the end of the article for particulars.

Using the code

As stated previously, FD.Delegate is a Boost.Function 'drop-in' replacement. So it is reasonable to refer to the online documentation of Boost.Function and especially Boost.Function tutorial for features of FD.Delegate. Just make sure to add '%FD_ROOT%/include' as a system include directory.

- Example #1 from Boost.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 from Boost.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.Delegate supports multicast and uses C#'s multicast syntax, operator += and operator -=.

#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 makes operator -= pretty much useless for removing a function object from multicast. FD.Delegate has add() and remove() member function pairs to remedy the issue. add() returns an instance of fd::multicast::token which 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 interface of Boost.Signals has been adopted, but has slightly different usage and syntax. The type of the combiner interface is not a part of FD.Delegate type, although it is for Boost.Signals, as the form of the template parameter when declaring a signal variable. Instead, FD.Delegate has 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 a void *. 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 to any_fxn_size is stored into any_fxn_pointer. any_fxn_pointer is implemented to make it able to store one out of three different pointer types -- a void data 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 than any_fxn_size, takes for an example when storing a member function pointer to virtual inherited class in MSVC, it is stored by allocating heap memory in the same way that Boost.Function does, as a non-fast delegate. However, in real world practices, virtual inheritance 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 in his article that 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. Using any_fxn_pointer introduced previously, we can significantly improve the syntax of the usage and, in turn, the degree of convenience. We just need to add an any_fxn_pointer as 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 of Boost.Function is 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 of Boost.Mem_fn and Boost.Bind. Boost.Function has 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 the Boost.Function detail, 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 about BIL (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 the get_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 why FD.Delegate does 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 for Boost.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. 1. Free function (including static member function)
FT02. 2-a. Member function on a pointer to an object of the type that the member function belongs to.
FT03. 2-b. Member function on a reference to an object of the type that the member function belongs to.
FT04. 2-c. Member function on a copy of an object of either the type that the member function belongs to or any type that supports get_pointer() overload (i.e. smart pointers).
FT05. 2-d. Member function bound with a pointer or a reference to an object of the type that the member function belongs to.
FT06. 2-e. Member function bound with a copy of an object of any type that supports get_pointer() overload (i.e. smart pointers).
FT07. 3-a. A pointer or a reference to a function object.
FT08. 3-b. A copy of a function object.
FT09. 3-c. Stateless function object.
FT10. Empty delegate.
FT11. 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 whole FD.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 an any_fxn_pointer to store all the previously categorized function types into a generic delegate. To make it easy to access and manipulate these void pointers, as well as any_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 the EqualityComparable concept to be able to compare between two delegates of the same type. As explained in the Frequently Asked Questions of Boost.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 the Boost.Function and FD.Delegate erasing 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.Delegate was made to return false by 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 possibly true, 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 returning false blindly, 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 of fd::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.Delegate will 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 hold false and never become true, as explained previously. Also note that we do not use the equality comparison operator for FD.Delegate to compare between the two. This is because it is not a valid expression in Boost.Function for same reasons as explained above. Furthermore, FD.Delegate is a Boost.Function 'drop-in' replacement. So instead, we use the equal_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 return false. 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 define fd::is_equality_comparable<> specialization for an anonymous function object. Boost.Signals solves this problem by providing the connect() 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 with FD.Delegate, but with a slightly different name tag. FD.Delegate has a member function called add(), which is almost equivalent to operator +=, but they are different in their return type.

operator += and operator -= return a self-reference, while add() returns an object called a 'token' and remove() returns the number of delegates in the multicast delegate. 'token' is very similar to what is 'connection' for Boost.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.Signals employes a technique, called combiner interface, to serve two purposes effectively: a) multiple return values management; b) multiple invocations control. See the Boost.Signals documentation for the combiner interface.

The idea and most of the implementation details of Boost.Signals's combiner interface are copied and incorporated into FD.Delegate. However, there is one big difference in their usage as multicast delegates. In order to be coincident with Boost.Function, the combiner interface of FD.Delegate is not designed to be a part of the delegate class type as a form of template parameter the way it is for Boost.Signals. Instead, FD.Delegate introduces 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 via multicast_call_iterator, which is copied and modified from Boost.Signals's slot_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 for Boost.Signals will work for FD.Delegate without a modification. It is obvious that FD.Delegate supports many useful features of Boost.Signals when it operates as a multicast delegate. However, it is never meant to replace Boost.Signals. Some very sophisticated features of Boost.Signals are not implemented in FD.Delegate, but it can still serve as more than a generic multicast delegate while it stands for a 'drop-in' replacement of Boost.Function.

Delegates comparison chart

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

Helper function template and class template

FD.Bind

FD.Bind is a set of helper function overloads that return an instance of FD.Delegate. Unlike Boost.Bind, FD.Bind can only be used to bind the object on which the member function call is made with the member function. FD.Bind can return a FD.Delegate instance of either function type FT05 or FT06, where FT05 is a fast delegate function type. Of course, FD.Delegate will work with Boost.Bind flawlessly 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 from Boost as well as FD.Delegate suffer 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.Resolution is 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 that Boost.Bind and Boost.Mem_fn only allow you to specify the return type to help function overload resolution. Also note that FD.Resolution is an independent utility class and thus can be used without FD.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.Delegate has been built based on Boost 1.33.1. As mentioned, FD.Delegate uses several type trait classes from Boost to transform type information. These features, especially remove_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
run Fail (1) Fail (1) Fail (2) Fail (2) Fail Pass Pass Pass
bind_eq
_test
run Pass Pass Pass Pass Fail Pass Pass Pass
bind
_fastcall
_mf_test
run Pass Pass Fail (2) Fail (2) Fail Pass Pass Pass
bind
_function
_test
run Pass Pass Pass Pass Pass Pass Pass Pass
bind
_stdcall
_mf_test
run Pass Pass Fail (2) Fail (2) Fail Pass Pass Pass
mem_fn
_cdecl_test
run Fail (1) Fail (1) Fail (2) Fail (2) Pass Pass Pass Pass
mem_fn
_derived
_test
run Pass Pass Pass Pass Pass Pass Pass Pass
mem_fn
_eq_test
run Pass Pass Pass Pass Pass Pass Pass Pass
mem_fn
_fastcall
_test
run Pass Pass Fail (2) Fail (2) Pass Pass Pass Pass
mem_fn
_stdcall
_test
run Pass Pass Fail (2) Fail (2) Pass Pass Pass Pass
mem_fn
_test
run Pass Pass Pass Pass Pass Pass Pass Pass
mem_fn
_void_test
run Pass Pass Pass Pass Pass Pass Pass Pass
empty
_delegate
run Pass Pass Pass Pass Pass Pass Pass Pass
allocator
_test
run Pass Pass Pass Pass Fail Fail Pass Pass
contains2
_test
run Pass Pass Pass Pass Pass Pass Pass Pass
contains
_test
run Pass Pass Pass Pass Fail Fail Pass Pass
delegate
_30
compile Pass Pass Pass Pass Pass Pass Pass Pass
delegate
_arith
_cxx98
run Pass Pass Pass Pass Fail Fail Pass Pass
delegate
_arith
_portable
run Pass Pass Pass Pass Pass Pass Pass Pass
delegate
_n_test
run Pass Pass Pass Pass Pass Pass Pass Pass
delegate
_ref
_cxx98
run Pass Pass Pass Pass Fail Fail Pass Pass
delegate
_ref
_portable
run Pass Pass Pass Pass Pass Pass Pass Pass
delegate
_test
run Pass Pass Pass Pass Pass Pass Pass Pass
delegate
_test
_fail1
compile
_fail
Pass Pass Pass Pass Pass Pass Pass Pass
delegate
_test
_fail2
compile
_fail
Pass Pass Pass Pass Pass Pass Pass Pass
lambda
_test
run Pass Pass Pass Pass Fail Fail Pass Pass
mem_fun
_cxx98
run Pass Pass Pass Pass Fail Fail Pass Pass
mem_fun
_portable
run Pass Pass Pass Pass Pass Pass Pass Pass
stateless
_test
run Pass Pass Pass Pass Pass Pass Pass Pass
std_bind
_cxx98
run Pass Pass Pass Pass Fail Fail Pass Pass
std_bind
_portable
run Pass Pass Pass Pass Pass Pass Pass Pass
sum_avg
_cxx98
run Pass Pass Pass Pass Fail Fail Pass Pass
sum_avg
_portable
run Pass Pass Pass Pass Pass Pass Pass Pass
function
_type
run Pass Pass Pass Pass Fail Pass Pass Pass
get
_pointer
run Pass Pass Pass Pass Pass Pass Pass Pass
multicast
_and
_empty
run Pass Pass Pass Pass Fail Pass Pass Pass
multicast
_call
_iterator
run Pass Pass Pass Pass Pass Pass Pass Pass
multiple
_inheritance
run Pass Pass Pass Pass Pass Pass Pass Pass
resolution
_select
_cxx98
run Pass Pass Pass Pass Fail Fail Pass Pass
resolution
_select
_portable
run Pass Pass Pass Pass Fail Fail Pass Pass
deletion
_test
run Pass Pass Pass Pass Pass Pass Pass Pass
signal
_n_test
run Pass Pass Pass Pass Pass Pass Pass Pass
signal_test run Pass Pass Pass Pass Fail Fail Pass Pass
type_info run Pass Pass Pass Pass Fail Pass Fail (4) Fail (4)
virtual
_inheritance
run Pass Pass Pass Pass Pass Pass Pass Pass

(1) Non-specified calling convention is treated exactly the same as __cdecl in 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_fn and Boost.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's any_pointer does not use the union trickery for the same reason.
    • ARTICLE: Uses a generic function pointer, generic_fxn *, to mark functions instead of void *.
    • CODE: Used the pre-defined (typedef void generic_fxn();) generic function pointer to mark functions intead of void *. This change has been made for target() 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 with Boost 1.33.1 and Boost 1.34 alpha.
    • CODE: Changed the way to apply get_pointer() to help VC6 for the overload resolution. Unfortunately, as a result, FT08 smart pointer type can not be used anymore.
    • CODE: Incorporated and modified Boost's get_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: Added any_fxn_pointer implementation to substitute the removed union trickery.
    • CODE: Added two test cases to check storing member function pointer to multiple inherited class and virtual inherited class.
    • CODE: Added simplify_mfn struct simliar to Don's SimplifyMemFunc to 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) gcc supports calling conventions, but does not effectively resolve the overload ambiguity among them.
(3) FD.Delegate has been built based on remove_xxx type traits from Boost 1.34 pre alpha instead of Boost 1.33.1 since the new version incorporates workarounds for VC6, VC7 and VC7.1 specific.

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

References

  • [Boost]. Boost.Function, Boost.Bind, Boost.Mem_fn and Boost.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's any_pointer does not use the union trickery for the same reason.
    • ARTICLE: Uses a generic function pointer, generic_fxn *, to mark functions instead of void *.
    • CODE: Used the pre-defined (typedef void generic_fxn();) generic function pointer to mark functions intead of void *. This change has been made for target() 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 with Boost 1.33.1 and Boost 1.34 alpha.
    • CODE: Changed the way to apply get_pointer() to help VC6 for the overload resolution. Unfortunately, as a result, FT08 smart pointer type can not be used anymore.
    • CODE: Incorporated and modified Boost's get_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: Added any_fxn_pointer implementation to substitute the removed union trickery.
    • CODE: Added two test cases to check storing member function pointer to multiple inherited class and virtual inherited class.
    • CODE: Added simplify_mfn struct simliar to Don's SimplifyMemFunc to 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

JaeWook Choi
Other
Canada Canada
No Biography provided

Comments and Discussions

 
Questionno, this is not a function table Pinmemberfrahmog10-Nov-13 20:57 
QuestionC++11 150 lines implementation with capturing lambdas support PinmemberVittorio Romeo6-Sep-13 0:52 
SuggestionC++11 variadic template version PinmemberVittorio Romeo12-Jan-13 8:12 
GeneralBoost 1.42.0 incompatibility Pinmemberripzonetriton24-Mar-10 13:22 
GeneralRe: Boost 1.42.0 incompatibility PinmemberMember 469200429-Nov-10 2:16 
GeneralRe: Boost 1.42.0 incompatibility Pinmembermwpowellhtx13-Feb-13 10:24 
QuestionHow do you read the graphs? [modified] PinmemberMember 82609318-Nov-09 12:30 
GeneralSome questions PinmemberAndreone13-Aug-09 3:19 
Hi,
 
I was wondering few things about your framework:
 
- how thread-safety is handled?
For example, is it safe to add an handler from a thread while removing an handler from an other thread?
 
- is it possible to have a predicate on a multicast delegate to manage return values in order to break the notification loop on a particular returned value?
 
Anyway, thank you for this great article.
Bye
Generalthrowing bad_function_call() when empty [modified] Pinmemberpakal0128-May-07 23:47 
GeneralRe: throwing bad_function_call() when empty PinmemberJaeWook Choi30-May-07 7:44 
GeneralCan't bind to COM interface PinmemberQuynh Nguyen19-May-07 12:55 
GeneralRe: Can't bind to COM interface PinmemberJaeWook Choi19-May-07 15:35 
GeneralRe: Can't bind to COM interface PinmemberQuynh Nguyen19-May-07 18:09 
GeneralRe: Can't bind to COM interface PinmemberJaeWook Choi20-May-07 2:36 
GeneralRe: Can't bind to COM interface PinmemberJaeWook Choi22-May-07 9:20 
GeneralRe: Can't bind to COM interface PinmemberQuynh Nguyen23-May-07 21:49 
Generalforgetting the type of a member function Pinmembersdevil19-May-07 23:13 
GeneralRe: forgetting the type of a member function PinmemberJaeWook Choi10-May-07 10:31 
GeneralRe: forgetting the type of a member function Pinmembersdevil113-May-07 3:54 
AnswerRe: forgetting the type of a member function PinmemberJaeWook Choi14-May-07 8:36 
GeneralRe: forgetting the type of a member function Pinmembersdevil114-May-07 11:20 
GeneralRe: forgetting the type of a member function PinmemberJaeWook Choi15-May-07 9:12 
GeneralRe: forgetting the type of a member function Pinmembersdevil115-May-07 11:22 
GeneralRe: forgetting the type of a member function PinmemberJaeWook Choi17-May-07 3:41 
GeneralAnother problem PinmemberQuynh Nguyen9-May-07 5:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140814.1 | Last Updated 1 Jun 2007
Article Copyright 2007 by JaeWook Choi
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid