|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionDon Clugston explained about the member function pointer and its behavior in a very well organized way in his article: "Member Function Pointers and the Fastest Possible C++ Delegates". In short, member function pointers can not be assigned into a Sometime later, Sergey Ryazanov came up with a fast C++ delegate which he found to be as fast as Don's There is one more great article about how to implement C++ delegates, here in CodeProject. It is: "Yet Another Generalized Functors Implementation in C++", by Aleksei Trunov. He explains and analyzes so well in detail about generalized functor requirements and the problems of the existing delegates. His article inspired me to start implementing my own fast delegate, which will be explained here below. By the way, these techniques are considered as 'fast' since they are able to avoid heap memory allocation to store member function pointers, unlike Another fast C++ delegate? So, what's new?No, there is nothing new in my fast delegate. All the features that I will show in this article already exist in other's implementations.
But, what if these features are all available in one? Doesn't that sound promising? Let's see! Type restoring without heap memory allocationLet's start off by revisiting Sergey's technique. It is only time the type information of the callee object is available when the member function pointer is getting assigned into the delegate; thus, some kind of mechanism is required to store the type info of the member function pointer into a non-typed universal form, and then to restore it whenever the delegate is being invoked later. Sergey uses a templatized static member function called ' class delegate { public: delegate() : object_ptr(0) , stub_ptr(0) {} template < class T, void (T::*TMethod)(int) > static delegate from_method(T* object_ptr) { delegate d; d.object_ptr = object_ptr; d.stub_ptr = &method_stub< T, TMethod >; // #1 return d; } void operator()(int a1) const { return (*stub_ptr)(object_ptr, a1); } private: typedef void (*stub_type)(void* object_ptr, int); void* object_ptr; stub_type stub_ptr; template < class T, void (T::*TMethod)(int) > static void method_stub(void* object_ptr, int a1) { T* p = static_cast< T* >(object_ptr); return (p->*TMethod)(a1); // #2 } }; From my own experience, I know that old compilers such as VC6 do not like to deal with templatized static member functions. At the same time, I also know that, in such a case, using a static member function of the nested template class will make those compilers happy. So, I changed it as shown below: class delegate { public: delegate() : object_ptr(0) , stub_ptr(0) {} template < class T, void (T::*TMethod)(int) > static delegate from_method(T* object_ptr) { delegate d; d.object_ptr = object_ptr; d.stub_ptr = &delegate_stub_t< T, TMethod >::method_stub; // #1 return d; } void operator()(int a1) const { return (*stub_ptr)(object_ptr, a1); } private: typedef void (*stub_type)(void* object_ptr, int); void* object_ptr; stub_type stub_ptr; template < class T, void (T::*TMethod)(int) > struct delegate_stub_t { static void method_stub(void* object_ptr, int a1) { T* p = static_cast< T* >(object_ptr); return (p->*TMethod)(a1); // #2 } }; }; One problem solved, but still need some 'C++ Standard-compliant but portable' mechanism which can substitute the non-typed template parameter of the member function pointer. The If you've read Rich's article "Callbacks in C++ Using Template Functors", you will realize that what I am doing here now is just a replication of what he did 10 years ago. But, after I read Aleksei's article "Yet Another Generalized Functors Implementation in C++ - An article on generalized functors implementation in C++", I realized that his meta meta-template technique to make the class behave differently according to the size of the member function pointer can be applied here and that I might be able to create something neat. // partial specialization version template < bool t_condition, typename Then, typename Else > struct If; template < typename Then, typename Else > struct If < true, Then, Else > { typedef Then Result; }; template < typename Then, typename Else > struct If < false, Then, Else > { typedef Else Result; }; // nested template structure version template < bool t_condition, typename Then, typename Else > struct If { template < bool t_condition_inner > struct selector; template < > struct selector < true > { typedef Then Result; }; template < > struct selector < false > { typedef Else Result; }; typedef typename selector < t_condition >::Result Result; }; By using the class delegate { public: delegate() : object_ptr(0) , stub_ptr(0), fn_ptr_(0), is_by_malloc(false) {} ~delegate() { if(is_by_malloc) { is_by_malloc = false; ::free(fn_ptr_); fn_ptr_ = 0; } } template < class T > struct fp_by_value { inline static void Init_(delegate & dg, T* object_ptr, void (T::*method)(int)) { typedef void (T::*TMethod)(int); dg.is_by_malloc = false; new (dg.buf_) TMethod(method); } inline static void Invoke_(delegate & dg, T* object_ptr, int a1) { typedef void (T::*TMethod)(int); TMethod const method = *reinterpret_cast < TMethod const * > (dg.buf_); return (object_ptr->*method)(a1); } }; template < class T > struct fp_by_malloc { inline static void init_(delegate & dg, T* object_ptr, void (T::*method)(int)) { typedef void (T::*TMethod)(int); dg.fn_ptr_ = ::malloc(sizeof(TMethod)); dg.is_by_malloc = true; new (dg.fn_ptr_) TMethod(method); } inline static void invoke_(delegate & dg, T* object_ptr, int a1) { typedef void (T::*TMethod)(int); TMethod const method = *reinterpret_cast < TMethod const * > (dg.fn_ptr_); return (object_ptr->*method)(a1); } }; template < class T > struct select_fp_ { enum { condition = sizeof(void (T::*)(int) <= size_buf) }; typedef fp_by_value<T> Then; typedef fp_by_malloc<T> Else; typedef typename If < condition, Then, Else >::Result type; }; template < class T > void from_method(T* object_ptr, void (T::*method)(int), int) { select_fp_<T>::type::Init_(*this, object_ptr, method); this->object_ptr = object_ptr; stub_ptr = &delegate_stub_t < T >::method_stub; } void operator()(int a1) const { return (*stub_ptr)(*this, object_ptr, a1); } private: enum { size_buf = 8 }; typedef void (*stub_type)(delegate const & dg, void* object_ptr, int); void* object_ptr; stub_type stub_ptr; union { void * fn_ptr_; unsigned char buf_[size_buf]; }; bool is_by_malloc; template < class T > struct delegate_stub_t { inline static void method_stub(delegate const & dg, void* object_ptr, int a1) { T* p = static_cast< T* >(object_ptr); return select_fp_<T>::type::invoke_(dg, p, a1); } }; }; Now, we have a 'C++ Standard-compliant and portable' substitution for Sergey's member function pointer as a non-typed template parameter. Two or three levels of indirection has been added, but the compiler with the descent inline optimization can opt it out and yield a code which is equivalent to Sergey's. In addition to this, we now have a binary representation of the member function pointer as an internal data structure so that it becomes possible to compare it with others. In other words, we can use delegates in STL containers which require its element for comparisons capability. (See the demo project included.) The size of the internal buffer can be customized by defining a proper macro before including the header file. I chose 8 bytes as the default size according to the size of the member function pointer table from Don's article. (I am mostly using MSVC, and has never used virtual inheritance, so 8 bytes is sufficient for myself, but again, you can customize the default buffer size.) Object Cloning Manager Stub (New)In the previous version, only the pointer (reference) to the bound object can be stored in the delegate for the member function pointer (argument binding) to be called on when the delegate is being invoked later. I decided to add the support for cloning the bound object in the delegate so that the member function pointer can be invoked on the internal copy of the bound object rather than on the pointer to the bound object. Again, only when the type information of the member function pointer or its bound object is available is when the member function pointer is being bound to the delegate. Therefore, some sort of type retaining the 'stub' for object cloning and destruction is required. A similar static member function of the nested template class, as shown above, to be used for invoking the member function pointer can be applied here again. typedef void * (*obj_clone_man_type)(delegate &, void const *); obj_clone_man_type obj_clone_man_ptr_; template < typename T > struct obj_clone_man_t { inline static void * typed_obj_manager_(delegate & dg, void const * untyped_obj_src) { T * typed_obj_src =const_cast < T * > (static_cast < T const * > (untyped_obj_src)); typed_obj_src; if(dg.obj_ptr_) { T * typed_obj_this = static_cast < T * > (dg.obj_ptr_); delete typed_obj_this; dg.obj_ptr_ = 0; } if(0 != typed_obj_src) { T * obj_new = new T(*typed_obj_src); dg.obj_ptr_ = obj_new; } T * typed_obj = static_cast < T * > (dg.obj_ptr_); typed_obj; return typed_obj; } }; // template<typename T> struct obj_clone_man_t obj_clone_man_ptr_ = &obj_clone_man_t<T>::typed_obj_manager_; An object can be cloned or destructed type safely using delegate dg1, dg2; // copy the internally cloned object of dg2 into dg1 (*dg1.obj_clone_man_ptr_)(dg1, dg2.obj_ptr_); // destroy the internally cloned object of dg1 (*dg1.obj_clone_man_ptr_)(dg1, 0); The size of the bound object is unknown, and it can be as small as a few bytes to several hundred bytes, or even more. Thus, heap memory allocation/deallocation is inevitable. This conflicts against the design criteria of 'fast delegates' since the main purpose of using a fast delegate is to avoid the use of heap memory at all costs. (A custom memory allocator, which will be addressed later, might play a decent role to soothe this issue.) Actually, I decided to introduce the cloning feature in my delegate for smart pointer support. Unlike in C#, we don't have a built-in garbage collector in C++, but we have smart pointers. In order to work with a smart pointer, it requires to be able to copy or destruct the smart pointer instance in a type safe manner (in other words, an appropriate assignment operator or destructor of the smart pointer must be called). We already have a 'stub' function to serve this purpose. But, there are still two more prerequisites / conditions to be fulfilled. (The idea is borrowed from
The following two versions of namespace fd { template<class T> inline T * get_pointer(T * p) { return p; } template<class T> inline T * get_pointer(std::auto_ptr<T> & p) { return p.get(); } } // namespace fd
#if defined(_MSC_VER) && _MSC_VER < 1300 // Even thogh get_pointer is defined in boost/shared_ptr.hpp, VC6 doesn't seem to // implement Koenig Lookup (argument dependent lookup) thus can't find the definition, // So we define get_pointer explicitly in fd namesapce to help the poor compiler namespace fd { template<class T> T * get_pointer(boost::shared_ptr<T> const & p) { return p.get(); } } #endif // #if defined(_MSC_VER) && _MSC_VER < 1300 Using the codePreferred syntax and Portable syntax#include "delegate.h" // Preferred Syntax fd::delegate < void (int, char *) > dg; #include "delegate.h" // Portable Syntax fd::delegate2 < void, int, char * > dg; The Preferred syntax can be only accepted by the relatively newer C++ Standard-compliant compilers such as VC7.1 or higher, or GNU C++ 3.XX, while the Portable syntax is supposed to be accepted by most compilers (I assume that my fast delegate can be easily ported to some other compilers without any significant problems, as it is proven to work even in the notorious VC6). (Remark: I tested my delegate only in VC6, VC7.1, and DEV-C++ 4.9.9.2 (Mingw/gcc 3.4.2).) When both Preferred syntax and Portable syntax are supported, it is just fine to use both syntaxes mixed (copy, comparison, copy-construction, assignment, and so on). Therefore, all the example code snippets hereafter will be demonstrated in Portable syntax. Wrapping three callable entitiesA callable entity has a function call
These three callable entities can be assigned to // ====================================================================== // example target callable entities // ====================================================================== class CBase1 { public: void foo(int n) const { } virtual void bar(int n) { } static void foobar(int n) { } virtual void virtual_not_overridden(int n) { } }; // ------------------------------ class CDerived1 : public CBase1 { std::string name_; public: explicit CDerived1(char * name) : name_(name) { } void foo(int n) const { name_; /*do something with name_ or this pointer*/ } virtual void bar(int n) { name_; /*do something with name_ or this pointer*/ } static void foobar(int n) { } void foofoobar(CDerived1 * pOther, int n) { name_; /*do something with name_ or this pointer*/ } }; // ------------------------------ void hello(int n) { } void hellohello(CDerived1 * pDerived1, int n) { } // ------------------------------ struct Ftor1 { // stateless functor void operator () (int n) { /*no state nor using this pointer*/ } }; struct Ftor2 { // functor with state string name_; explicit Ftor2(char * name) : name_(name) { } void operator () (int n) { name_; /*do something with name_ or this pointer*/ } }; Free functions// copy-constructed fd::delegate1 < void, int > dg1(&::hello); fd::delegate1 < void, int > dg2 = &CBase1::foobar; dg1(123); dg2(234); // assigned fd::delegate < void, int > dg3; dg3 = &CDerived1::foobar; dg3(345); Member functions (Member function adapter)CBase1 b1; CDerived1 d1("d1"); // copy-constructed fd::delegate2 < void, CBase1 *, int > dg1(&CBase1::foo); // pointer adapter fd::delegate2 < void, CBase1 &, int > dg2 = &CBase1::bar; // reference adapter dg1(&b1, 123); dg2(b1, 234); // assigned fd::delegate2 < void, CDerived1 *, int > dg3; dg3 = &CDerived1::foo; dg3(&d1, 345); Probably, this might not be what you wanted to achieve. You might want to declare a delegate more like: // excerpted from boost::function online document template < typename P > R operator()(cv-quals P& x, Arg1 arg1, Arg2 arg2, ..., ArgN argN) const { return (*x).*mf(arg1, arg2, ..., argN); } It is very interesting that the member function can be adapted and invoked as shown above. While I was using Functors (Function objects) (Updated)Ftor2 f2("f2"); // copy-constructed bool dummy = true; fd::delegate1 < void, int > dg1(f2, dummy); // store the cloned functor internally fd::delegate1 < void, int > dg2(&f2, dummy); // store only the pointer to the functor dg1(123); // (internal copy of f2).operator ()(123); dg2(234); // (&f2)->operator ()(234); // assigned ( special operator <<= ) fd::delegate1 < void, int > dg3, dg4, dg5, dg6; dg3 <<= f2; // store the cloned functor internally dg4 <<= &f2; // store only the pointer to the functor dg5 <<= Ftor1(); // store the cloned functor internally dg6 <<= &Ftor1(); // store only the pointer to the functor which is temporary dg3(345); // (internal copy of f2).operator ()(345); dg4(456); // (&f2)->operator () (456); dg5(567); // (internal copy of temporary Ftor1).operator ()(567); dg6(678); // (&temporary Ftor1 that has been // already destroyed)->operator ()(678); Runtime error! I didn't consider including functor support in the beginning. When I changed the plan (even after I completed a tedious and boring code duplication for a calling convention), I tried to implement the normal assignment operator ( Ftor1 f1; fd::delegate2 < void, Ftor1 *, int > dg1(&Ftor1::operator ()); dg1(&f1, 123); Silly me! I hope you will be happy with A delegate can't be a delegate without something for which it represents. That is, the wrapped target callable entity must be in a valid state when the delegate is being invoked. This behavior is somewhat different from how But in the new version, I added a cloning bound object feature, therefore the syntax of Member function argument binding (Updated)CBase1 b1; // copy-constructed fd::delegate1 < void, int > dg1(&CBase1::foo, b1); // storing the cloned bound object internally fd::delegate1 < void, int > dg2(&CBase1::foo, &b1); // storing the pointer to the bound object dg1(123); // (internal copy of b1).foo(123); dg2(234); // (&b1)->foo(123); // bind member fd::delegate1 < void, int > dg3, dg4; dg3.bind(&CBase1::bar, b1); // storing the cloned bound object internally dg4.bind(&CBase1::bar, &b1); // storing the pointer to the bound object dg3(345); // (internal copy of b1).bar(345); dg4(456); // (&b1)->bar(456); // fd::bind() helper function fd::delegate1 < void, int > dg5 = fd::bind(&CBase1::foo, b1, _1); // storing the cloned bound object internally fd::delegate1 < void, int > dg6 = fd::bind(&CBase1::foo, &b1, _1); // storing the pointer to the bound object dg5(567); // (internal copy of b1).foo(567); dg6(678); // (&b1)->foo(678); A member function pointer is required to be called on the callee object of the same type. The callee object is bound as a reference (pointer) so that it should be in a valid state when the delegate is being invoked. It is the caller's responsibility to make the callee object intact to be called. In the new version, the bound object can be cloned internally, or even a smart pointer can be bound for automatic memory management. std::auto_ptr<CBase1> spb1(new CBase1); fd::delegate1 < int, int > dg1; dg1.bind(&CBase1::foo, spb1); dg1(123); // get_pointer(internal copy of spb1)->foo(123); boost::shared_ptr<CBase1> spb2(new CBase1); fd::delegate1 < int, int > dg2(&CBase1::foo, spb2); dg2(234); // get_pointer(internal copy of spb2)->foo(234); The #include < boost/function.hpp > #include < boost/bind.hpp > using boost::function1; using boost::bind; CBase1 b1; function1 < void, int > fn = bind( &CBase1::foo, &b1, _1 ); The above can be easily converted to: #include "delegate.h" using fd::delegate1; using fd::bind; CBase1 b1; delegate1 < void, int > fn = bind( &CBase1::foo, &b1, _1 ); But, be aware that the placeholder fd::make_delegate() helper functionIt can be useful when passing a delegate as a function parameter. typedef fd::delegate1 < void, int > MyDelegate1; typedef fd::delegate2 < void, CDerived *, int > MyDelegate2; void SomeFunction1(MyDelegate1 myDg) { } void SomeFunction2(MyDelegate2 myDg) { } CBase1 b1; CDerived1 d1("d1"); // free function version SomeFunction1(fd::make_delegate(&::hello)); SomeFunction2(fd::make_delegate(&::hellohello); // member function adapter version SomeFunction1(fd::make_delegate((CBase1 *)0, &CBase1::foobar)); SomeFunction2(fd::make_delegate((CDerived *)0, &CDerived1::foo)); // member function argument binding version SomeFunction1(fd::make_delegate(&CBase1::foo, &b1)); SomeFunction2(fd::make_delegate(&CDerived1::foofoobar, &d1); SomeFunction1(fd::make_delegate(&CBase1::foo, b1)); SomeFunction2(fd::make_delegate(&CDerived1::foofoobar, d1); But the member function adapter version of The Comparisons and Miscellaneoustypedef fd::delegate1 < void, int > MyDelegate; CBase1 b1, b2; CDerived1 d1("d1"); // ---------------------------------------------------------------------- MyDelegate dg1(&CBase1::foo, &b1); MyDelegate dg2 = &::hello; if(dg1 == dg2) cout << "dg1 equals to dg2" << endl; else cout << "dg1 does not equal to dg2" << endl; if(dg1 > dg2) { cout << "dg1 is greater than dg2" << endl; dg1(123); } else if(dg1 < dg2) { cout << "dg2 is greater than dg1" << endl; dg2(234); } // ---------------------------------------------------------------------- MyDelegate dg3 = dg1; MyDelegate dg4(&CBase1::foo, &b2); // both function pointer and its bound callee object pointer // stored in dg1 is the same as those stored in dg3 if(0 == dg1.compare(dg3)) { // this test return true dg3(345); } if(0 == dg1.compare(dg3, true)) { // this test return true as well dg3(456); } // ---------------------------------------------------------------------- // function pointer stored in dg1 is the same as that stored in dg4 // but their bound callee object pointers are not the same if(0 == dg1.compare(dg4)) { // this test return true dg4(567); } if(0 == dg1.compare(dg4, true)) { // this test return fail dg4(678); } // ---------------------------------------------------------------------- if(dg2 != 0) { // this test return true cout << "dg2 is not empty" << endl; } if(dg2) { // this test return true cout << "dg2 is not empty" << endl; } if(!!dg2) { // this test return true cout << "dg2 is not empty" << endl; } if(!dg2.empty()) { // this test return true cout << "dg2 is not empty" << endl; } // ---------------------------------------------------------------------- dg1.swap(dg2); MyDelegate(dg2).swap(dg1); // dg1 = dg2; MyDelegate().swap(dg1); // dg1.clear(); dg2.clear(); dg3 = 0; // ---------------------------------------------------------------------- if(dg3.empty()) { try { dg3(789); } catch(std::exception & e) { cout << e.what() << endl; } // 'call to empty delegate' exception } // ---------------------------------------------------------------------- CBase1 * pBase = 0; // binding null callee object on purpose dg3.bind(&CBase1::foo, pBase); try { FD_ASSERT( !dg3.empty() ); dg3(890); } // 'member function call on no object' exception catch(std::exception & e) { cout << e.what() << endl; } Comparing two delegates means comparing the memory address of the function pointer stored internally, and it does not really mean anything special. But, making it possible allowed my delegate to be used in an STL container seamlessly. As it is a 'fast' delegate in 'most' cases, we don't need to worry too much about performance degrade while the delegate is being copied inside an STL container by the value semantic. const correctnessCBase1 b1; CBase1 const cb1; // -------------------------------------------------- // argument binding MyDelegate dg1(&CBase1::foo, &b1); MyDelegate dg2(&CBase1::foo, &cb1); MyDelegate dg3(&CBase1::bar, &b1); // compile error! const member function can // not be called on non-const object // MyDelegate dg4(&CBase1::bar, &cb1); dg1(123); dg2(234); dg3(345); // -------------------------------------------------- // member function adapter fd::delegate2<INT, int *, CBase1> dg4(&CBase1::foo); fd::delegate2<INT, int *, CBase1> dg5(&CBase1::bar); fd::delegate2<INT, int *, CBase1 const> dg6(&CBase1::foo); // compile error! non-const member function // can not be used for const member function adapter // fd::delegate2<INT, int *, CBase1 const> dg7(&CBase1::bar); dg4(&b1, 456); // compile error! const object cannot be used // non-const member function adapter // dg4(&cb1, 456); dg5(&b1, 567); // compile error! const object cannot be used // non-const member function adapter // dg5(&cb1, 567); dg6(&b1, 678); dg6(&cb1, 678); Platform specific calling conventionCalling convention is not a C++ standard feature, but it can't be just ignored as the Win32 API and the COM API use it. From an implementation point of view, it is just a matter of boring and tedious replication of the same code. By default, none of the platform specific calling conventions are enabled. To enable it, the relevant macro needs to be defined before "delegate.h" is included.
(Remark) Calling convention support only works in MSVC at the time, due to the lack of my understanding of gcc. Type-check relaxationTemplate parameter types passed over to a delegate are very strictly checked, but this could be too much in real life. There might be a situation where we want to treat a bunch of By defining the
If any of the above conditions can not be met, the compiler will complain about it (compile-time warning and/or error messages). CBase1 b1; // // int CBase1::foo(int) const; // int CBase1::bar(int); // fd::delegate1 < int, long > dg1(&CBase1::foo, &b1); dg1(123); The above delegate definition is theoretically equivalent to the following function definition: CBase1 b1; int fd_delegate1_dg1(long l) { return b1.foo(l); } fd_delegate1_dg1(123); This shows why the three conditions are required to be fulfilled for // compile warning! : 'return' : conversion from '' to '', possible loss of data fd::delegate1 < float, long > dg2(&CBase1::bar, &b1); is equivalent to: float fd_delegate1_dg2(long l) { // compile warning! : possible loss of data return b1.bar(l); } An ' // compile error! : cannot convert parameter 3 from 'char *' to 'int' fd::delegate1 < int, char * > dg3(&CBase1::foo, &b1); is equivalent to: int fd_delegate1_dg3(char * ch) { // compile error! : cannot convert parameter 'ch' from 'char *' to 'int' return b1.foo(ch); } and the compiler will issue an error since ' CDerived1 d1("d1"); // // class CDerived1 : public CBase1 { }; // fd::delegate2 < int, CDerived1 *, long > dg5(&CBase1::bar); is equivalent to: int fd_delegate2_dg5(CDerived1 * pd1, long l) { return pd1->bar(l); } and upcasting from ' fd::make_delegate() for type-check relaxation mode (Removed)[Obsolete] When The purpose of using static_assert (Debugging support)When a delegate is assigned or bound to a function pointer, the compiler generates the appropriate function call Custom memory allocator support (New)In the new version, my delegate can use services from any custom memory allocator when it requires to allocate or deallocate memory either for storing the member function pointer whose size is greater than the internal buffer size or for storing the cloned bound object. I included a Final wordsIf the speed is your only concern, define I also included a guide on how to extract a simplified version from the full version for those who want to see the implementation details after the macro expansion. Definitionnamespace fd { // ---------------------------------------------------------------------- class bad_function_call; class bad_member_function_call; // ====================================================================== // // class delegateN (Portable Syntax) // // ====================================================================== template < typename R,typename T1,typename T2,...,typename TN, typename Alloocator = std::allocator < void > , size_t t_countof_pvoid = 2 > class delegateN; // ---------------------------------------------------------------------- // default c'tor delegateN< R, T1, T2, ..., TN >::delegateN(); // ---------------------------------------------------------------------- // copy c'tor for 0 delegateN< R, T1, T2, ..., TN >::delegateN(implClass::clear_type const *); // ---------------------------------------------------------------------- // copy c'tor delegateN< R, T1, T2, ..., TN >::delegateN(delegateN< R, T1, T2, ..., TN > const & other); // ---------------------------------------------------------------------- // function copy c'tor delegateN< R, T1, T2, ..., TN >::delegateN(R (*fn)(T1, T2, ..., TN); // ---------------------------------------------------------------------- // member function adapter copy c'tors // ,where T1 can be trivially converted to either U * or U & delegateN< R, T1, T2, ..., TN >::delegateN(R (U::*mfn)(T2, T3, ..., TN)); // ,where T1 can be trivially converted to one // of U * or U const * or U & or U const & delegateN< R, T1, T2, ..., TN >::delegateN(R (U::*mfn)(T2, T3, ..., TN) const); // ---------------------------------------------------------------------- // member function argument binding copy c'tors delegateN< R, T1, T2, ..., TN >::delegateN(R (U::*mfn)(T1, T2, ..., TN), T & obj); delegateN< R, T1, T2, ..., TN >::delegateN(R (U::*mfn)(T1, T2, ..., TN) const, T & obj); delegateN< R, T1, T2, ..., TN >::delegateN(R (U::*mfn)(T1, T2, ..., TN), T * obj); delegateN< R, T1, T2, ..., TN >::delegateN(R (U::*mfn)(T1, T2, ..., TN) const, T * obj); // ---------------------------------------------------------------------- // functor copy c'tors template< typename Functor > delegateN< R, T1, T2, ..., TN >::delegateN(Functor & ftor, bool/* dummy*/); template< typename Functor > delegateN< R, T1, T2, ..., TN >::delegateN(Functor * ftor, bool/* dummy*/); // ---------------------------------------------------------------------- // assignment from 0 delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator = (implClass::clear_type const *); // ---------------------------------------------------------------------- // assignment operator delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator = (delegateN< R, T1, T2, ..., TN > const & other); // ---------------------------------------------------------------------- // function assignment operator delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator = (R (*fn)(T1, T2, ..., TN); // ---------------------------------------------------------------------- // member function adapter assignment operators // ,where T1 can be trivially converted to either U * or U & delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator = (R (U::*mfn)(T2, ..., TN)); // ,where T1 can be trivially converted to one // of U * or U const * or U & or U const & delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator = (R (U::*mfn)(T2, ..., TN) const); // ---------------------------------------------------------------------- // member function argument binding assignment operators delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator = (R (U::*mfn)(T1, T2, ..., TN), T & obj); delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator = (R (U::*mfn)(T1, T2, ..., TN) const, T & obj); delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator = (R (U::*mfn)(T1, T2, ..., TN), T * obj); delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator = (R (U::*mfn)(T1, T2, ..., TN) const, T * obj); // ---------------------------------------------------------------------- // functor assignment operators template< typename Functor > delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator <<= (Functor & ftor); template< typename Functor > delegateN< R, T1, T2, ..., TN > & delegateN< R, T1, T2, ..., TN >::operator <<= (Functor * ftor); // ---------------------------------------------------------------------- // invocation operator result_type operator ()(T1 p1, T2 p2, ..., TN pN) const; // ---------------------------------------------------------------------- // swap void delegateN< R, T1, T2, ..., TN >::swap(delegateN & other); // ---------------------------------------------------------------------- // clear void delegateN< R, T1, T2, ..., TN >::clear(); // ---------------------------------------------------------------------- // empty bool delegateN< R, T1, T2, ..., TN >::empty() const; // ---------------------------------------------------------------------- // comparison for 0 bool operator == (implClass::clear_type const *) const; bool operator != (implClass::clear_type const *) const; // ---------------------------------------------------------------------- // compare int compare(delegateN const & other, bool check_bound_object = false) const; // comparison operators bool operator == (delegateN< R, T1, T2, ..., TN > const & other) const; bool operator != (delegateN< R, T1, T2, ..., TN > const & other) const; bool operator <= (delegateN< R, T1, T2, ..., TN > const & other) const; bool operator < (delegateN< R, T1, T2, ..., TN > const & other) const; bool operator >= (delegateN< R, T1, T2, ..., TN > const & other) const; bool operator > (delegateN< R, T1, T2, ..., TN > const & other) const; // ====================================================================== // class delegate (Preferred Syntax) // ====================================================================== template< typename R,typename T1,typename T2,...,typename TN, typename Allocator = std::allocator< void >,size_t t_countof_pvoid = 2 > class delegate< R (T1, T2, ..., TN), Allocator, t_countof_pvoid >; // // the same set of member functions as fd::delegateN of Portable Syntax // // ====================================================================== // fd::make_delegate() // ====================================================================== // make_delegate for function template< typename R,typename T1,typename T2,...,typename TN,typename U,typename T > delegateN< R, T1, T2, ..., TN > make_delegate(R (*fn)(T1, T2, ..., TN)); // ---------------------------------------------------------------------- // make_delegate for member function adapter template< typename R,typename T2,...,typename TN,typename U,typename T > delegateN< R, T *, T2, ..., TN > make_delegate(T *, R (U::*mfn)(T2, ..., TN)); template< | ||||||||||||||||||||