注意,使用最新的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 ofLibrary Technical Report (). 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.

  • Jae's Fast Delegate

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.Functionfor features ofFD.Delegate. Just make sure to add '%FD_ROOT%/include' as a system include directory.

- Example #1 fromBoost.Function.

#include 
#include
struct int_div{ float operator()(int x, int y) const { return ((float)x)/y; };};int main(){ fd::delegate
f; f = int_div(); std::cout << f(5, 3) << std::endl; // 1.66667 return 0;}

- Example #2 fromBoost.Function.

#include 
#include
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
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 
#include
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
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 
#include
#include
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
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.ofBoost.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 
#include
#include
template
struct maximum{ typedef T result_type; template
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
struct aggregate_values{ typedef Container result_type; template
Container operator()(InputIterator first, InputIterator last) const { return Container(first, last); }};int main(){ fd::delegate2
dg_max; dg_max += std::plus
(); dg_max += std::multiplies
(); dg_max += std::minus
(); dg_max += std::divides
(); std::cout << dg_max(5, 3, maximum
()) << std::endl; // prints 15 std::vector
vec_result = dg_max(5, 3, aggregate_values
>()); 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
( any.fxn_ptr ) )( 1, 1.0f );}void test2(any_fxn_pointer any, foobar * pfb){ ( pfb->*reinterpret_cast
( any.mfn_ptr ) )( 1, 1.0f );}void main(){ any_fxn_pointer any; any.fxn_ptr = reinterpret_cast
( &hello ); test1( any ); foobar fb; any.mfn_ptr = reinterpret_cast
( &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
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 inthat 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
struct mem_fn_stub { static void invoke(void const * obj_ptr, int a0) { T * obj = static_cast
( const_cast
( obj_ptr ) ); (obj->*Fxn)( a0 ); } }; template
struct mem_fn_const_stub { static void invoke(void const * obj_ptr, int a0) { T const * obj = static_cast
( obj_ptr ); (obj->*Fxn)( a0 ); } }; template
struct function_stub { static void invoke(void const *, int a0) { (*Fxn)( a0 ); } };public: delegate() : obj_ptr_( 0 ), stub_ptr_( 0 ) { } template
void from_function(T * obj) { obj_ptr_ = const_cast
( obj ); stub_ptr_ = &mem_fn_stub
::invoke; } template
void from_function(T const * obj) { obj_ptr_ = obj; stub_ptr_ = &mem_fn_const_stub
::invoke; } template
void from_function() { obj_ptr_ = 0; stub_ptr_ = &function_stub
::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
( pfb ); dg( 1 ); // (pfb->*&foobar::foo)( 1 ); dg.from_function
( 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
struct mem_fn_stub { static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0) { T * obj = static_cast
( const_cast
( obj_ptr ) ); (obj->*reinterpret_cast
( any.mfn_ptr ) )( a0 ); } }; template
struct function_stub { static void invoke(void const *, any_fxn_pointer any, int a0) { (*reinterpret_cast
( any.fxn_ptr ) )( a0 ); } };public: delegate() : obj_ptr_( 0 ), any(), stub_ptr_( 0 ) { } template
void from_function(void (T::*fxn)(int ), T * obj) { typedef void (T::*TFxn)(int); obj_ptr_ = const_cast
( obj ); any_.mfn_ptr = reinterpret_cast
( fxn ); stub_ptr_ = &mem_fn_stub
::invoke; } template
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
( fxn ); stub_ptr_ = &mem_fn_stub
::invoke; } void from_function(void (*fxn)(int)) { typedef void (*TFxn)(int); obj_ptr_ = 0; any_.fxn_ptr = reinterpret_cast
( fxn ); stub_ptr_ = &function_stub
::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 about. 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
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
( fxn ); } static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0) { T * obj = static_cast
( const_cast
( obj_ptr ) ); (obj->*reinterpret_cast
( 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, ©_obj, &delete_obj, &is_empty, }; return &static_table; } }; template
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
( fxn ); } static void invoke(void const * obj_ptr, any_fxn_pointer any, int a0) { T * obj = static_cast
( const_cast
( obj_ptr ) ); ( get_pointer(*obj)->*reinterpret_cast
( 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
( obj_ptr_src ) ); } static void delete_obj(void const * obj_ptr) { // Deletes the pointed object. delete static_cast
( obj_ptr ); } static bool is_empty() { return false; } static fxn_table const * get_table() { static fxn_table const static_table = { &invoke, ©_obj, &delete_obj, &is_empty, }; return &static_table; } }; template
struct function_stub { static void init(void const ** obj_pptr, any_fxn_pointer & any, TFxn fxn) { *obj_pptr = 0; any.fxn_ptr = reinterpret_cast
( fxn ); } static void invoke(void const *, any_fxn_pointer any, int a0) { (*reinterpret_cast
( 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, ©_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, ©_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
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
::value), mem_fn_stub
::type, TFxn>, mem_fn_obj_stub
> type; }; select_stub::type::init( &obj_ptr_, any_, obj, fxn ); tbl_ptr_ = select_stub::type::get_table(); } template
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
::value), mem_fn_stub
::type, TFxn>, mem_fn_obj_stub
> 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
::init( &obj_ptr_, any_, fxn ); tbl_ptr_ = function_stub
::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
T * get_pointer(boost::shared_ptr
const & t){ return t.get();}void main(){ boost::shared_ptr
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. 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 supportsget_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 supportsget_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
dg1; foobar fb; foobar * pfb = &fb; boost::shared_ptr
spfb( new foobar ); dg1 = &hello; // FT01 dg1( 1 ); // hello( 1 ); dg1 = &foobar::bar; // FT01 dg1( 1 ); // foobar::bar( 1 ); delegate
dg2; dg2 = &foobar::foo; // FT02 dg2( pfb, 1 ); // (pfb->*&foobar::foo)( 1 ); delegate
dg3; dg3 = &foobar::foo; // FT03 dg3( fb, 1 ); // (fb.*&foobar::foo)( 1 ); delegate
dg4; dg4 = &foobar::foo; // FT04 dg4( fb, 1 ); // ((copy of fb).*&foobar::foo)( 1 ); delegate
, 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
( &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 theconcept to be able to compare between two delegates of the same type. As explained in theofBoost.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
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
dg1; dg1 += std::negate
(); token tk1 = dg1.add( std::bind1st( std::plus
(), 3 ) ); // (A) assert( tk1.valid() == true ); assert( dg1.count() == 2 ); dg1.remove( std::bind1st( std::plus
(), 3 ) ); // Can not remove the delegate added in the // line (A) std::bind1st( std::plus
(), 3 ) assert( dg1.count() == 2 ); tk1.remove(); // Removes the delegate added in the line // (A) std::bind1st( std::plus
(), 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 the.

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
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
dg1; dg1 += std::plus
(); dg1 += std::multiplies
(); dg1 += std::minus
(); dg1 += std::divides
(); 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
struct first_positive { typedef T result_type; template
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
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
dg_positive; dg_positive += std::plus
(); dg_positive += std::multiplies
(); dg_positive += std::minus
(); dg_positive += noisy_divide
(); assert(dg_positive(3, -5, first_positive
()) == 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 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 supportsget_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 supportsget_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.Functiondrop-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.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 
#include
#include
#include
struct foobar{ void foo(int) { }};void main(){ fd::delegate
dg1; foobar fb; foobar * pfb = &fb; boost::shared_ptr
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

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
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
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
( &foobar::foo, pfb, _1 ); // (A) boost::bind
( &foobar::foo, pfb, _1 ); // (B) // boost::bind
( &foobar::bar, pfb, _1 ); // Can't solve the ambiguity. boost::mem_fn
( &foobar::foo ); // (A) boost::mem_fn
( &foobar::foo ); // (B) // boost::mem_fn
( &foobar::bar ); // Can't solve the ambiguity. boost::function
fn; fd::bind
( &foobar::foo, pfb ); // (A) fd::bind
( &foobar::foo, pfb ); // (B) fd::bind
( &foobar::bar, pfb ); // (C) fd::bind
( &foobar::bar, pfb ); // (D) fd::delegate
dg; dg = fd::bind
( &foobar::bar, pfb ); // (C) dg = fd::bind
( &foobar::bar, pfb ); // (D) dg.bind
( &foobar::bar, pfb ); // (C) dg.bind
( &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 
using fd::resolution;void main(){ foobar fb; foobar * pfb = &fb; boost::bind( resolution
::select( &foobar::foo ), pfb, _1 ); // (A) boost::bind( resolution
::select( &foobar::foo ), pfb, _1 ); // (B) boost::bind( resolution
::select( &foobar::bar ), pfb, _1 ); // (C) boost::bind( resolution
::select( &foobar::bar ), pfb, _1 ); // (D) boost::mem_fn( resolution
::select( &foobar::foo ) ); // (A) boost::mem_fn( resolution
::select( &foobar::foo ) ); // (B) boost::mem_fn( resolution
::select( &foobar::bar ) ); // (C) boost::mem_fn( resolution
::select( &foobar::bar ) ); // (D) boost::function
fn; fn = boost::bind( resolution
::select( &foobar::bar ), pfb, _1 ); // (C) fn = boost::bind( resolution
::select( &foobar::bar ), pfb, _1 ); // (D) fd::bind( resolution
::select( &foobar::foo ), pfb ); // (A) fd::bind( resolution
::select( &foobar::foo ), pfb ); // (B) fd::bind( resolution
::select( &foobar::bar ), pfb ); // (C) fd::bind( resolution
::select( &foobar::bar ), pfb ); // (D) fd::delegate
dg; dg = fd::bind( resolution
::select( &foobar::bar ), pfb ); // (C) dg = fd::bind( resolution
::select( &foobar::bar ), pfb ); // (D) dg.bind( resolution
::select( &foobar::bar ), pfb ); // (C) dg.bind( resolution
::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
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__cdeclin Intel C++ compiler for WIN32. Therefore do not define both (non-specified and__cdecl) at the same time.

References

.
Boost.Function,
Boost.Bind,
Boost.Mem_fnand
Boost.Signals . "The Impossibly Fast C++ Delegates" by Sergey Ryazanov . "Member Function Pointers and the Fastest Possible C++ Delegates" by Don Clugston. . "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
fromBoost 1.34 pre alphainstead ofBoost 1.33.1since the new version incorporatesfor VC6, VC7 and VC7.1 specific.
(4) VC71 & VC8.
unds for VC6, VC7 and VC7.1 specific.
(4) VC71 & VC8.

References

  • .Boost.Function,Boost.Bind,Boost.Mem_fnandBoost.Signals
  • . "The Impossibly Fast C++ Delegates" by Sergey Ryazanov
  • . "Member Function Pointers and the Fastest Possible C++ Delegates" by Don Clugston.
  • . "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