[fusion::vector] Forwarding packed arguments by reference

In a object factory I'm converting to using boost::fusion I have a problem with passing argument by reference with a fusion vector. Let's explaing a bit... UI function to get an object from a name and a list of c'tor arguments is something like template<typename T1> /* one argument case */ static Base* object(detail::KeyRef nm, T1 a1) { typedef boost::fusion::vector<T1> values; values v(boost::fusion::vector_tie(a1)); return detail::Factory_p<Base>::instance(). template doObject<values>(nm, v); } template<typename T1, typename T2> /* two arguments case */ static Base* object(detail::KeyRef nm, T1 a1, T2 a2) { typedef boost::fusion::vector<T1, T2> values; values v(boost::fusion::vector_tie(a1, a2)); return detail::Factory_p<Base>::instance(). template doObject<values>(nm, v); } I want to easy the user from the burden to specifying template parameters, so use template parameters deduction. Because of function templates does not support template default arguments we are forced to use one of the above template for each number of arguments. Although we have many UI 'object()' templates I would like to use only one doObject<> template to avoid duplicating all the inner workings of the factory for each argument number. To do this input arguments are 'packed' in a fusion vector and unpacked just before to invoke the function object that actually creates the object: template<typename Values> Base* doObject(KeyRef nm, Values& v) const { --- cut-- const SignatureClass<Base, Values>* p = this->find<Values>(r); if (p) return (*p)(v); -- cut --- } Where the operator() in (overloaded) SignatureClass is defined as: template<typename Base, typename Values typename FnType> struct CreatorClass: public SignatureClass<Base, Values> { typedef typename FnType::result_type result_type; CreatorClass(const FnType& fn) : functor(fn) {} virtual result_type operator()(Values& v) const { return boost::fusion::invoke_function_object(functor, v); } FnType functor; }; Now the problem is that I'm not able to find a way to pass arguments by reference. As example, if I have a class: class Test { public: Test(int& cnt) { cnt++; } }; trying the following will fail: int objCnt = 6; Test* t = Factory::object(objCnt); cout << objCnt; // Is still 6 not 7!! This is expected since I pass by copy in object(detail::KeyRef nm, T1 a1) But it fails to update objCnt also if I explicitly set the template parameter type: Test* t = Factory::object<int&>(objCnt); How can I modify the code above to let pack arguments in a vector, handling always passing the packed vector by reference and finally call invoke_function_object() ? Thanks Marco

Marco Costalba wrote:
template<typename T1, typename T2> /* two arguments case */ static Base* object(detail::KeyRef nm, T1 a1, T2 a2) {
<...>
I want to easy the user from the burden to specifying template parameters, so use template parameters deduction.
Template argument deduction doesn't work like this. Gotta say 'T1&' (this one only allows LValues) or 'T1 const&' (this one also allows RValues and is always const). We call it "The Forwarding Problem", see http://tinyurl.com/6enrw for details.
template<typename Base, typename Values typename FnType> struct CreatorClass: public SignatureClass<Base, Values> {
typedef typename FnType::result_type result_type;
CreatorClass(const FnType& fn) : functor(fn) {}
virtual result_type operator()(Values& v) const {
return boost::fusion::invoke_function_object(functor, v); }
FnType functor; };
Just BTW: virtual operator() almost always indicates that one's better off using Boost.Function...
int objCnt = 6; Test* t = Factory::object(objCnt);
Of course, because the reference isn't deduced (see above).
How can I modify the code above to let pack arguments in a vector, handling always passing the packed vector by reference and finally call invoke_function_object() ?
Try plugging your function object into boost::unfused_generic. Regards, Tobias

On 9/17/07, Tobias Schwinger <tschwinger@isonews2.com> wrote:
Try plugging your function object into boost::unfused_generic.
Hi Tobias, I have found a solution this way (and it works): These are the overloads according to the number of arguments (I just show 2 of them) template<typename T1> static Base* object(detail::KeyRef nm, T1 a1) { typedef boost::fusion::vector<T1> Args; typedef typename detail::tie_sequence<Args>::type Values; Values v(boost::fusion::vector_tie(a1)); return detail::Dispatcher<Base>::instance().template doObject<Values, Args>(nm, v); } template<typename T1, typename T2> static Base* object(detail::KeyRef nm, T1 a1, T2 a2) { typedef boost::fusion::vector<T1, T2> Args; typedef typename detail::tie_sequence<Args>::type Values; Values v(boost::fusion::vector_tie(a1, a2)); return detail::Dispatcher<Base>::instance().template doObject<Values, Args>(nm, v); } Then this is the _only_ inner common functions that is called by all the overloads: template<typename Values, typename Args> Base* doObject(KeyRef nm, Values& v) const { Range r; if (!find_range<Args>(nm, r)) return NULL; /* no function with the same number of arguments */ const SignatureClass<Base, Args>* p = find<Args>(r); if (p) return (*p)(v); /* perfect match found */ return NULL; } And finally this is the functor wrapper from which the operator() is invoked: /* 'FactoryClass' is subclassed from 'SignatureClass' and stores * the functor object used to create the requested object, * will be called due to dynamic polymorphism */ template<typename Base, typename Args, typename Functor> struct FactoryClass: public SignatureClass<Base, Args> { typedef typename tie_sequence<Args>::type Values; typedef typename Functor::result_type result_type; FactoryClass(const Functor& fn) : _functor(fn) {} virtual result_type operator()(Values& v) const { return boost::fusion::invoke_function_object(_functor, v); } Functor _functor; }; Where the base SignatureClass is: /* 'SignatureClass' is subclassed from StorableType and identifies * an unique argument list type (without return value). Used by * runtime argument checking */ template<typename Base, typename Args> struct SignatureClass : public StorableClass { // at runtime values passed to operator() are stored // in a tied vector, so set the signature accordingly typedef typename tie_sequence<Args>::type Values; virtual Base* operator()(Values&) const = 0; }; That is derived from the argument agnostic, storable in a map, base class: /* 'StorableType' is the common base class to store pointers * to different type of creators, dynamic_cast<> at runtime * will be used to check the (hidden) derived type */ struct StorableClass { virtual ~StorableClass() {} }; BTW I studied your factory and also your suggestion to use a map as a dispatcher to create a complete object factory from your one. The problem is that the classes must be registered at instantation time. IOW your factory (+ a map framework to act as dispatcher) does not seem to support the concept of adding new classes at runtime, everything must be already known at instantation/definition time. Please correct me if I'm wrong. Thanks Marco

On 9/18/07, Marco Costalba <mcostalba@gmail.com> wrote:
template<typename Values, typename Args> Base* doObject(KeyRef nm, Values& v) const { Range r; if (!find_range<Args>(nm, r)) return NULL; /* no function with the same number of arguments */
const SignatureClass<Base, Args>* p = find<Args>(r);
This is the core of all the stuff. At runtime the passed in argument types 'Args' are checked against the factory wrappers stored in a map to see if one of them support the same type of arguments. template<typename Args> const SignatureClass<Base, Args>* find(const Range& r) const { for (const_iterator it = r.first; it != r.second; ++it) { const SignatureClass<Base, Args>* p = check_type<Args>((*it).second); if (p) return p; } return NULL; } And finally: template<typename Args> const SignatureClass<Base, Args>* check_type(const StorableClass* p) const { // check c'tor arguments type at runtime return dynamic_cast<const SignatureClass<Base, Args>*>(p); } All this dynamic plomorphism stuff is, again, necessary (at least I haven't found nothing better) _only_ because supported classes are not known at factory instantation time. If it was known I could use a fusion map to store everything and the trick is done. But a fusion map cannot be changed at runtime! If you add new classes to your map you have a _new_ map type. So you cannot foreseen a map variable as a member data. That's the problem. class factory { .... a_fusion_map_type myFactoriesMap; template<typename F> void add_factory(const F& f) { myFactoriesMap << f; // not possible because 'a_fusion_map_type' changes } } Thanks Marco

On 9/18/07, Marco Costalba <mcostalba@gmail.com> wrote:
template<typename Values, typename Args> Base* doObject(KeyRef nm, Values& v) const { Range r; if (!find_range<Args>(nm, r)) return NULL; /* no function with the same number of arguments */
const SignatureClass<Base, Args>* p = find<Args>(r);
This is the core of all the stuff. At runtime the passed in argument types 'Args' are checked against the factory wrappers stored in a map to see if one of them support the same type of arguments. template<typename Args> const SignatureClass<Base, Args>* find(const Range& r) const { for (const_iterator it = r.first; it != r.second; ++it) { const SignatureClass<Base, Args>* p = check_type<Args>((*it).second); if (p) return p; } return NULL; } And finally: template<typename Args> const SignatureClass<Base, Args>* check_type(const StorableClass* p) const { // check c'tor arguments type at runtime return dynamic_cast<const SignatureClass<Base, Args>*>(p); } All this dynamic plomorphism stuff is, again, necessary (at least I haven't found nothing better) _only_ because supported classes are not known at factory instantation time. If it was known I could use a fusion map to store everything and the trick is done. But a fusion map cannot be changed at runtime! If you add new classes to your map you have a _new_ map type. So you cannot foreseen a map variable as a member data. That's the problem. class factory { .... a_fusion_map_type myFactoriesMap; template<typename F> void add_factory(const F& f) { myFactoriesMap << f; // not possible because 'a_fusion_map_type' changes } } Thanks Marco

Hi Marco, Marco Costalba wrote:
I have found a solution this way (and it works):
These are the overloads according to the number of arguments (I just show 2 of them)
template<typename T1> static Base* object(detail::KeyRef nm, T1 a1)
This one will fail to compile with a non-copyable argument for 'a1' (unless 'T1' is specified explicitly to be a reference).
BTW I studied your factory and also your suggestion to use a map as a dispatcher to create a complete object factory from your one.
OK, so we're basically talking about this one (from boost.user, some time ago - cited from memory and possibly full of errors) // assuming 'an_abstract' base and 'a_concrete', default // constructible sub class std::map< std::string, boost::function< an_abstract*() > > map_factories; map_factories["a_concrete"] = boost::factory<a_concrete>(); , arent't we?
The problem is that the classes must be registered at instantation time.
The code above does not create objects of the classes - it just generates (or references already-generated) code to do so.
IOW your factory (+ a map framework to act as dispatcher) does not seem to support the concept of adding new classes at runtime, everything must be already known at instantation/definition time.
Well, C++ never ever generates code at runtime... However, you sure can add another element to that map: // assuming 'another_concrete', default constructible // sub class map_factories["another_concrete"] = boost::factory<another_concrete>(); Maybe you mean that 'boost::factory<a_concrete>' and 'boost::factory< another_concrete>'are distinct (even incompatible) types? The clue is that both can be turned into objects of type 'boost::function<an_abstract*() >' to become polymorphic runtime entities. Marco Costalba wrote (in the other post):
All this dynamic plomorphism stuff is, again, necessary (at least I haven't found nothing better) _only_ because supported classes are not known at factory instantation time. If it was known I could use a fusion map to store everything and the trick is done.
Boost.Function is basically a factorization of a virtual operator(). See its documentation for details: http://www.boost.org/libs/function
But a fusion map cannot be changed at runtime! If you add new classes to your map you have a _new_ map type. So you cannot foreseen a map variable as a member data.
I know :-). Use an STL map like I did in the example (guessing you might have misread a sidenote of mine about using a fusion::map in addition to a std::map to support several signatures). Regards, Tobias

Hi Tobias, On 9/18/07, Tobias Schwinger <tschwinger@isonews2.com> wrote:
template<typename T1> static Base* object(detail::KeyRef nm, T1 a1)
This one will fail to compile with a non-copyable argument for 'a1' (unless 'T1' is specified explicitly to be a reference).
Yes, but something like static Base* object(detail::KeyRef nm, T1& a1) will fail with temporaries and something like static Base* object(detail::KeyRef nm, const T1& a1) will fail when c'tor arguments should be modified by the c'tor. I choose the first one because, as you noted, is the only one for whom exists a simple and 'intuitive' workaround for those (rare) cases: specify explicitly to be a reference. BTW, a little digression, if the standard would allow to elide argument passing for inline functions the real solution would be: template<typename T1> inline Base* shell(detail::KeyRef nm, T1 a1) { return object<T1>(nm, a1); } template<typename T1> Base * object<T1>(detail::KeyRef nm, traits<T1>::parameter a1); In this case the 'shell' is just a way to allow to typedef the type T inside a trait before to evaluate the argument and avoid the user to specify it explicitly...but unfortunately this does not work....I tried ;-)
Maybe you mean that 'boost::factory<a_concrete>' and 'boost::factory< another_concrete>'are distinct (even incompatible) types?
The clue is that both can be turned into objects of type 'boost::function<an_abstract*() >' to become polymorphic runtime entities.
I think your scheme will have troubles with multi argument support because you need to foreseen more abstract bases: an_abstract*(T1) an_abstract*(T1,T2) etc.. All with different types and this breaks single base polymorphic assumption -> you need one map for each argument arity. Perhaps for each argument arity _and_ types. Marco
participants (2)
-
Marco Costalba
-
Tobias Schwinger