[smart_ptr] operator ->*

Is there any reason as to why the boost smart pointers don't have overloaded ->* operators? With just a few template function overloads they could account for any member function type given that it has a low number of parameters (you'd need a separate template for each different number of parameters). Right now, users have to explicitly dereference the smart pointers and then use .* which is fairly unintuitive. Granted, using member function pointers on smart pointers isn't horrbily common, but it's not too out of the ordinary. I suppose the forwarding of arguments could be an issue as it could imply extra copies, but is that really a reason not to support the operation at all? Forgive me if this has been brought up before. -- -Matt Calabrese

"Matt Calabrese" <rivorus@gmail.com> writes:
I suppose the forwarding of arguments could be an issue as it could imply extra copies, but is that really a reason not to support the operation at all? Forgive me if this has been brought up before.
With Paul M.'s help I have optimized the forwarding generator I've been using, and I generalized it so this sort of thing should be less painful and generate less code in all its permutations (if everyone uses the same template). Enjoy... -- Dave Abrahams Boost Consulting www.boost-consulting.com

On 05/23/2006 06:55 AM, David Abrahams wrote:
"Matt Calabrese" <rivorus@gmail.com> writes:
I suppose the forwarding of arguments could be an issue as it could imply extra copies, but is that really a reason not to support the operation at all? Forgive me if this has been brought up before.
With Paul M.'s help I have optimized the forwarding generator I've been using, and I generalized it so this sort of thing should be less painful and generate less code in all its permutations (if everyone uses the same template). Enjoy...
I noticed that fusion_v2 uses some sort of boost_pp to forward args: http://tinyurl.com/klfwu and something similar is done for vector and maybe elsewhere in fusion. Does anyone plan to generalize these forwarding generator's so that they can be used in fusion_v2 ( and eventually as an alternative to the one's in http://tinyurl.com/o7zlg )?

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of David Abrahams
"Matt Calabrese" <rivorus@gmail.com> writes:
I suppose the forwarding of arguments could be an issue as it could imply extra copies, but is that really a reason not to support the operation at all? Forgive me if this has been brought up before.
With Paul M.'s help I have optimized the forwarding generator I've been using, and I generalized it so this sort of thing should be less painful and generate less code in all its permutations (if everyone uses the same template). Enjoy...
In this particular case (operator->*), I don't think combinatorial generation is necessary. You have the type of the pointer to member and, therefore, all of the exact parameter types. You can implement it with a general closure (possibly with bind) so that the implementation of operator->* is reduced to a single and simple function: // ---------- closure.hpp ---------- // #if !BOOST_PP_IS_ITERATING #ifndef CLOSURE_HPP #define CLOSURE_HPP "closure.hpp" #include <boost/preprocessor/facilities/apply.hpp> #include <boost/preprocessor/iteration/iterate.hpp> #include <boost/preprocessor/repetition/enum_binary_params.hpp> #include <boost/preprocessor/repetition/enum_params.hpp> #include <boost/preprocessor/repetition/enum_trailing_params.hpp> #include <boost/preprocessor/seq/elem.hpp> #include <cassert> // identity template<class T> struct identity { typedef T type; }; // enable_if template<bool, class R = void> struct enable_if { }; template<class R> struct enable_if<true, R> : identity<R> { }; // map_integral template<class T, T X> struct map_integral { static const T value = X; }; template<class T, T X> const T map_integral<T, X>::value; // is_function template<class T> class is_function { private: template<class U> static char check(U (*)[1]); template<class U> static char (& check(...))[2]; public: static const bool value = sizeof(check<T>(0)) != 1; }; template<class T> const bool is_function<T>::value; template<> class is_function<void> : public map_integral<bool, false> { }; template<class T> struct is_function<T&> : public map_integral<bool, true> { }; // closure namespace detail { template<class, class = void> class closure; template<class D, class C> class closure<D C::*, typename enable_if<!is_function<D>::value>::type> : public identity<D&> { }; } template<class T> class closure : public detail::closure<T> { }; #define cv(n) \ BOOST_PP_APPLY(BOOST_PP_SEQ_ELEM( \ n, (BOOST_PP_NIL)((const))((volatile))((const volatile)) \ )) \ /**/ #define BOOST_PP_ITERATION_PARAMS_1 (3, (0, 3, CLOSURE_HPP)) #include BOOST_PP_ITERATE() #undef cv #endif #elif BOOST_PP_ITERATION_DEPTH() == 1 #define i BOOST_PP_FRAME_ITERATION(1) template<class D, class C> inline typename enable_if< !is_function<D>::value, cv(i) D& >::type make_closure(cv(i) C* obj, cv(i) D C::* data) { assert(obj && data); return obj->*data; } #define BOOST_PP_ITERATION_PARAMS_2 \ (3, (0, CLOSURE_MAX_ARITY ? CLOSURE_MAX_ARITY : 15, CLOSURE_HPP)) \ /**/ #include BOOST_PP_ITERATE() #undef i #else #define j BOOST_PP_ITERATION() #define member(id) \ R (C::* BOOST_PP_APPLY(id))(BOOST_PP_ENUM_PARAMS(j, P)) cv(i) \ /**/ template<class R, class C BOOST_PP_ENUM_TRAILING_PARAMS(j, class P)> class closure<member(BOOST_PP_NIL)> { public: typedef closure type; inline closure(cv(i) C* obj, member((func))) : obj_((assert(obj), obj)), func_((assert(func), func)) { return; } inline R operator()(BOOST_PP_ENUM_BINARY_PARAMS(j, P, p)) const { return (obj_->*func_)(BOOST_PP_ENUM_PARAMS(j, p)); } private: cv(i) C* obj_; member((func_)); }; template<class R, class C BOOST_PP_ENUM_TRAILING_PARAMS(j, class P)> inline closure<member(BOOST_PP_NIL)> make_closure(cv(i) C* obj, member((func))) { assert(obj && func); return closure<member(BOOST_PP_NIL)>(obj, func); } #undef member #undef j #endif // ---------- test.cpp ---------- // #include "closure.hpp" #include <iostream> #include <memory> template<class T, class M, class C> inline typename closure<M C::*>::type operator->*(const std::auto_ptr<T>& sp, M C::* member) { return make_closure(&*sp, member); } struct A { inline A(void) : x(0), y(123) { return; } int x; const int y; int get(void) { return x; } void set(int y) { x = y; return; } }; int main(void) { int A::* px = &A::x; int (A::* pget)(void) = &A::get; void (A::* pset)(int) = &A::set; std::auto_ptr<A> sp(new A); sp->*px = 1; (sp->*pset)(sp->*px + 1); std::cout << (sp->*pget)() << ' ' << sp->*&A::y << '\n'; return 0; } The code generation yields specializations of 'closure' for different arities and cv-qualifications of pointer-to-member-function types. It also yields overloads of 'make_closure' for different arities and cv-qualifications of pointer-to-member-function types and overloads of 'make_closure' for different cv-qualifications of pointer-to-data-member types. The specializations of 'closure' and overloads of 'make_closure' are fairly straightforward. E.g. for a pointer-to-const-qualified-ternary-member-function: template<class R, class C, class P0, class P1, class P2> class closure<R (C::*)(P0, P1, P2) const> { public: typedef closure type; inline closure(const C* obj, R (C::* func)(P0, P1, P2) const) : obj_((assert(obj), obj)), func_((assert(func), func)) { return; } inline R operator()(P0 p0, P1 p1, P2 p2) const { return (obj_->*func_)(p0, p1, p2); } private: const C* obj_; R (C::* func_)(P0, P1, P2) const; }; template<class R, class C, class P0, class P1, class P2> inline closure<R (C::*)(P0, P1, P2) const> make_closure(const C* obj, R (C::* func)(P0, P1, P2) const) { assert(obj && func); return closure<R (C::*)(P0, P1, P2) const>(obj, func); } The overloads of 'make_closure' for pointer-to-data-members types are similarly simple. E.g. for a pointer-to-const-qualified-data-member: template<class D, class C> inline typename enable_if< !is_function<D>::value, const D& >::type make_closure(const C* obj, const D C::* data) { assert(obj && data); return obj->*data; } The '::type' member of 'closure' and the 'make_closure' functions are used to generalize the syntax of an operator->* overload into one function. When you say something like: template<class T, class M, class C> inline typename closure<M C::*>::type operator->*(const std::auto_ptr<T>& sp, M C::* member) { return make_closure(&*sp, member); } The return type 'closure<M C::*>::type' is a (possibly cv-qualified) reference to 'M' if 'M C::*' is a pointer-to-data-member. If 'M C::*' is a pointer-to-member-function, the return type 'closure<M C::*>::type' is 'closure<M C::*>'. The 'make_closure' overloads similarly generalize the difference between pointers-to-member-functions and pointers-to-data-members. Basically, the implementation above does all the work necessary to implement operator->* once and for all (for any number of different smart pointers). The only things that one might need to change for a particular smart pointer is how the smart pointer is passed to operator->* and how the raw pointer is accessed: template<class T, class M, class C> inline typename closure<M C::*>::type operator->*(boost::shared_ptr<T> sp, M C::* member) { return make_closure(sp.get(), member); } template<class T> class smart_ptr { // ... public: template<class M, class C> inline typename closure<M C::*>::type operator->*(M C::* member) { return make_closure(get(), member); } // ... }; Long story short, I don't think that there is a serious forwarding problem here, nor is there a significant code size overhead on a per smart pointer implementation to supply this operator. One other note before I forget: If a smart pointer supplies an implicit conversion to T*, none of the above is necessary at all because the built-in operator->* is a free function (allowing the conversion on the first argument type). E.g. if you have: template<class T> class smart_ptr { // ... public: inline operator T*(void) const { return get(); } // ... }; ...operator->* should already work (without any forwarding at all). Regards, Paul Mensonides

Paul Mensonides wrote:
The return type 'closure<M C::*>::type' is a (possibly cv-qualified) reference to 'M' if 'M C::*' is a pointer-to-data-member. If 'M C::*' is a pointer-to-member-function, the return type 'closure<M C::*>::type' is 'closure<M C::*>'. The 'make_closure' overloads similarly generalize the difference between pointers-to-member-functions and pointers-to-data-members.
Basically, the implementation above does all the work necessary to implement operator->* once and for all (for any number of different smart pointers). The only things that one might need to change for a particular smart pointer is how the smart pointer is passed to operator->* and how the raw pointer is accessed:
I once implemented something similar, but my version wasn't nearly as elegant as yours (and I suspect you wrote yours in a lot less time). So I'm feeling very churlish for pointing out that it doesn't deal with a couple of cases: struct A { A() : x(3); int x; }; int main() { int A::*member = &A::x; std::auto_ptr<A const> ptr(new A); std::cout<<(ptr->*member)<<"\n"; } and also: struct base { int x; int func() { return 1; } }; struct derived : base {}; int main() { std::auto_ptr<derived> x(new derived); std::cout<<(x->*(&derived::func))(); } I've attached a quick attempt at getting them to work. With it, the implementation of operator->* becomes: template<class T, class M, class C> inline typename closure<T, M C::*>::type operator->*( const std::auto_ptr<T>& sp, M C::* member) { return make_closure(&*sp, member); }

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Daniel James
I once implemented something similar, but my version wasn't nearly as elegant as yours (and I suspect you wrote yours in a lot less time). So I'm feeling very churlish for pointing out that it doesn't deal with a couple of cases:
I'm not surprised that I missed something. Luckily we have a bunch of smart people here.
I've attached a quick attempt at getting them to work.
With it, the implementation of operator->* becomes:
template<class T, class M, class C> inline typename closure<T, M C::*>::type operator->*( const std::auto_ptr<T>& sp, M C::* member) { return make_closure(&*sp, member); }
Does it work? Regards, Paul Mensonides

On 24/05/06, Paul Mensonides <pmenso57@comcast.net> wrote:
template<class T, class M, class C> inline typename closure<T, M C::*>::type operator->*( const std::auto_ptr<T>& sp, M C::* member) { return make_closure(&*sp, member); }
Does it work?
Well, it passes the tests I tried it with (the ones from your post and a couple of others I quickly wrote). But it doesn't support the style of calling suggested by Peter, because it requires the type of the pointee. But that is only needed to tell if it's const or volatile - which should be fairly easy to get from the pointer. I'll have a go at doing that when I get the chance, which probably means this weekend.

Paul Mensonides wrote: [...]
template<class T, class M, class C> inline typename closure<M C::*>::type operator->*(const std::auto_ptr<T>& sp, M C::* member) { return make_closure(&*sp, member); }
[...]
Basically, the implementation above does all the work necessary to implement operator->* once and for all (for any number of different smart pointers). The only things that one might need to change for a particular smart pointer is how the smart pointer is passed to operator->* and how the raw pointer is accessed:
template<class T, class M, class C> inline typename closure<M C::*>::type operator->*(boost::shared_ptr<T> sp, M C::* member) { return make_closure(sp.get(), member); }
template<class T> class smart_ptr { // ... public: template<class M, class C> inline typename closure<M C::*>::type operator->*(M C::* member) { return make_closure(get(), member); } // ... };
You don't have to change the implementation of ->* depending on the smart pointer type. *p is always the dereference operation, and (proxy references notwithstanding) &*p is always a raw pointer to the pointee. get() is needed for cases when the source is NULL, but we need not worry about them here, as ->* isn't allowed. So: template<class P, class M, class C> inline typename closure<M C::*>::type operator->*( P p, M C::* member) { return make_closure( &*p, member ); } It would work for iterators, too.

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Peter Dimov
You don't have to change the implementation of ->* depending on the smart pointer type. *p is always the dereference operation, and (proxy references notwithstanding) &*p is always a raw pointer to the pointee. get() is needed for cases when the source is NULL, but we need not worry about them here, as ->* isn't allowed.
Okay.
So:
template<class P, class M, class C> inline typename closure<M C::*>::type operator->*( P p, M C::* member) { return make_closure( &*p, member ); }
It would work for iterators, too.
How would this affect generic code? A use of this operator would likely be dependent and therefore might only be found through the ADL in the second phase. Regards, Paul Mensonides

Paul Mensonides wrote:
So:
template<class P, class M, class C> inline typename closure<M C::*>::type operator->*( P p, M C::* member) { return make_closure( &*p, member ); }
It would work for iterators, too.
How would this affect generic code? A use of this operator would likely be dependent and therefore might only be found through the ADL in the second phase.
I tend to not use ->* in generic code anyway since (*p).* is much more likely to work. :-)
participants (6)
-
Daniel James
-
David Abrahams
-
Larry Evans
-
Matt Calabrese
-
Paul Mensonides
-
Peter Dimov