[type_map] Interest in container indexed by type?

Hi people, A couple years ago I wrote a container indexed by type, which I've come to consider a basic building block for reusable code. Effectively, code can use "type_maps" to handle an arbitrary structure, without needing to know member identifiers, and gets the speed benefit of compile-time access resolution. A simple implementation follows: namespace NS { namespace Private { class no_type2 { }; class no_type3 { }; class no_type4 { }; class no_type5 { }; template <typename T> class type_map_base { public: void operator=(const T& t) { t_ = t; } operator const T&() const { return t_; } private: T t_; }; template<> class type_map_base<no_type2> { }; template<> class type_map_base<no_type3> { }; template<> class type_map_base<no_type4> { }; template<> class type_map_base<no_type5> { }; } template <typename T1, typename T2 = Private::no_type2, typename T3 = Private::no_type3, typename T4 = Private::no_type4, typename T5 = Private::no_type5> class type_map : public Private::type_map_base<T1>, public Private::type_map_base<T2>, public Private::type_map_base<T3>, public Private::type_map_base<T4>, public Private::type_map_base<T5> { public: using Private::type_map_base<T1>::operator=; using Private::type_map_base<T2>::operator=; using Private::type_map_base<T3>::operator=; using Private::type_map_base<T4>::operator=; using Private::type_map_base<T5>::operator=; }; } // sample usage... #include <iostream> #include <string> using namespace std; int main() { NS::type_map<int, string> tmid; tmid = 3; tmid = "hello"; string s = tmid; cout << (int)tmid << ' ' << (string)tmid << ' ' << s << endl; cout << sizeof(tmid) << " vs " << sizeof(int) << '+' << sizeof(string) << endl; } I've found typemaps useful for writing things like a logging system that allows arbitrary criterion for cataloging errors. To store several data items of the same type, simply wrap each instance in a distinct class (possibly using the template w/ unique number idiom). Implementation note: the empty base class optimisation makes it practical to have a single template, avoiding typelists (mpl sequences), preprocessing etc.. Cheers, Tony ___________________________________________________________ How much free photo storage do you get? Store your holiday snaps for FREE with Yahoo! Photos http://uk.photos.yahoo.com

block for reusable code. Effectively, code can use "type_maps" to handle an arbitrary structure, without needing to know member identifiers, and gets the speed Yes, but you'd need to know the types, and if a type is duplicated, as you say below, it would have to be wrapped in some class to make it unique. [snip] benefit of compile-time access resolution. int main() { NS::type_map<int, string> tmid; tmid = 3; tmid = "hello"; string s = tmid; cout << (int)tmid << ' ' << (string)tmid << ' ' << s << endl; cout << sizeof(tmid) << " vs " << sizeof(int) << '+' << sizeof(string) << endl; } So if I had: <--------------- struct sname_t { string first_m; string last_m; }; sname_t a_sname; a_sname.first_m="first"; a_sname.last_m="last"; ---------------
Hi Tony, On 10/31/2005 04:12 AM, Tony D wrote: [snip] then, to convert this to using typemaps, I would do: <--------------- struct fname_t: public string{...}; struct lname_t: public string{...}; NS:type_map<fname_t,lname_t> mname_t; mname_t a_mname; a_mname = fname_t("first"); a_mname = lname_t("last");
--------------
So I don't see that I've gained much, at least in this case. [snip]
cataloging errors. To store several data items of the same type, simply wrap each instance in a distinct class (possibly using the template w/ unique number idiom). [snip] -regards, Larry

Larry wrote:
So if I had: struct sname_t { string first_m; string last_m; }; sname_t a_sname; a_sname.first_m="first"; a_sname.last_m="last"; then, to convert this to using typemaps, I would do: struct fname_t: public string{...}; struct lname_t: public string{...}; NS:type_map<fname_t,lname_t> mname_t; mname_t a_mname; a_mname = fname_t("first"); a_mname = lname_t("last"); So I don't see that I've gained much, at least in this case.
Firstly, I left too much of the fleshing out possible from the type_map abstraction implicit, which certainly left room for your question (sorry). Consider: a_mname = x.get_last_name(); Benefits include: * Code using type_maps can speculatively store values (with mismatches reported by the compiler) without needing to know specifics of the types or field identifiers: e.g. template <class FUNCTOR> ...(const FUNCTOR& functor)... a_mname = functor(x, y, z); * You can iterate over the fields. An implementation of the visitor pattern just calls an accept() method on the type_map_base templates wrapping each element, with the default for no_typeN being empty. This allows things like generic serialisation, assuming the fields/members/elements are serialisable. * You can enquire whether a particular type is in the map (the T* find<T> implementation if/elses through is_same<TN,T> tests, returning a pointer or 0). * Removal of the redundant field-name + implicit-type information inherent after evolving from the type-unsafe: a_sname.first_m = x.get_last_name(); to use of distinct types for fields, ensuring a match such as: a_sname.last_m = x.get_last_name(); Cheers, Tony P.S. Hope the following (228 lines) aren't considered excessive... #include <typeinfo> #include <boost/type_traits/is_same.hpp> namespace NS { namespace Private { class no_type1 { }; class no_type2 { }; class no_type3 { }; class no_type4 { }; template <typename T> class type_map_base { public: enum { COUNT = 1 }; void operator=(const T& t) { t_ = t; } operator const T&() const { return t_; } template <class VISITOR> void accept(const VISITOR& visitor) { visitor.on_visit(t_); } T* get_ptr() { return &t_; } const T* get_ptr() const { return &t_; } private: T t_; }; // have to explicitly incorporate apply_functor to retain EBCO w/ g++ template<> struct type_map_base<no_type1> { enum { COUNT = 0 }; template <class V> inline void accept(const V&) { } void* get_ptr() { return 0; } }; template<> struct type_map_base<no_type2> { enum { COUNT = 0 }; template <class V> inline void accept(const V&) { } void* get_ptr() { return 0; } }; template<> struct type_map_base<no_type3> { enum { COUNT = 0 }; template <class V> inline void accept(const V&) { } void* get_ptr() { return 0; } }; template<> struct type_map_base<no_type4> { enum { COUNT = 0 }; template <class V> inline void accept(const V&) { } void* get_ptr() { return 0; } }; } template <typename T0, typename T1 = Private::no_type1, typename T2 = Private::no_type2, typename T3 = Private::no_type3, typename T4 = Private::no_type4> class type_map : public Private::type_map_base<T0>, public Private::type_map_base<T1>, public Private::type_map_base<T2>, public Private::type_map_base<T3>, public Private::type_map_base<T4> { public: enum { COUNT = Private::type_map_base<T0>::COUNT + Private::type_map_base<T1>::COUNT + Private::type_map_base<T2>::COUNT + Private::type_map_base<T3>::COUNT + Private::type_map_base<T4>::COUNT }; typedef T0 type_1; typedef T1 type_2; typedef T2 type_3; typedef T3 type_4; typedef T4 type_5; template <typename VISITOR> void accept(const VISITOR& visitor) { visitor.on_before(); Private::type_map_base<T0>::accept(visitor); if (COUNT >= 2) { visitor.on_between(); Private::type_map_base<T1>::accept(visitor); if (COUNT >= 3) { visitor.on_between(); Private::type_map_base<T2>::accept(visitor); if (COUNT >= 4) { visitor.on_between(); Private::type_map_base<T3>::accept(visitor); if (COUNT >= 5) { visitor.on_between(); Private::type_map_base<T4>::accept(visitor); } } } } visitor.on_after(); } // return pointer to member else NULL template <typename T> T* find() { if (boost::is_same<T, T0>::value) return (T*)Private::type_map_base<T0>::get_ptr(); else if (boost::is_same<T, T1>::value) return (T*)Private::type_map_base<T1>::get_ptr(); else if (boost::is_same<T, T2>::value) return (T*)Private::type_map_base<T2>::get_ptr(); else if (boost::is_same<T, T3>::value) return (T*)Private::type_map_base<T3>::get_ptr(); else if (boost::is_same<T, T4>::value) return (T*)Private::type_map_base<T4>::get_ptr(); else return 0; } template <int N> const std::type_info& ct_get_type_info() const { switch(N) { case 0: return typeid(T0); case 1: return typeid(T1); case 2: return typeid(T2); case 3: return typeid(T3); case 4: return typeid(T4); default: return typeid(void); } } const std::type_info& rt_get_type_info(int n) const { switch(n) { case 0: return typeid(T0); case 1: return typeid(T1); case 2: return typeid(T2); case 3: return typeid(T3); case 4: return typeid(T4); default: return typeid(void); } } using Private::type_map_base<T0>::operator=; using Private::type_map_base<T1>::operator=; using Private::type_map_base<T2>::operator=; using Private::type_map_base<T3>::operator=; using Private::type_map_base<T4>::operator=; }; } #include <iostream> #include <string> using namespace std; class Printer { public: void on_before() const { cout << "{ "; } template <typename T> void on_visit(const T& t) const { cout << t; } void on_between() const { cout << ", "; } void on_after() const { cout << " }" << endl; } }; int main() { NS::type_map<int, float, string> tmid; tmid = 3; tmid = (float)2.71828; tmid = "hello"; string s = tmid; cout << "operator T(): " << (int)tmid << ' ' << (float)tmid << ' ' << s << endl; cout << "sizeof comparison: " << sizeof(tmid) << " vs " << sizeof(int) << '+' << sizeof(float) << '+' << sizeof(string) << endl; cout << "::COUNT " << NS::type_map<int, string>::COUNT << endl; cout << "Printing..." << endl; Printer printer; tmid.accept(printer); float* p_float = tmid.find<float>(); cout << "p_float " << p_float << endl; double* p_double = tmid.find<double>(); cout << "p_double " << p_double << endl; } ___________________________________________________________ To help you stay safe and secure online, we've developed the all new Yahoo! Security Centre. http://uk.security.yahoo.com

For the record, another important benefit of type_maps is that code can be instantiated with a type_map, and a template <typename T> fn(T& t) can perform operations on the T field in the typemap, including storing the function's argument, or setting it to the retrieved value. By way of contrast, for code instantiated with an arbitrary structure there's no knowledge of field identifiers, so equivalent field access can only be achieved if that structure is directly exposed through the API. I was curious with the MPL guys could see any use in having a typedef in a type_map for a mpl::vector, but it's pretty clear nobody reading this gives a hoot about "type_map"s as described, so I'll put a sock in it. Cheers, Tony ___________________________________________________________ Yahoo! Messenger - NEW crystal clear PC to PC calling worldwide with voicemail http://uk.messenger.yahoo.com

Tony D <tony_in_da_uk@yahoo.co.uk> writes:
For the record, another important benefit of type_maps is that code can be instantiated with a type_map, and a template <typename T> fn(T& t) can perform operations on the T field in the typemap, including storing the function's argument, or setting it to the retrieved value.
By way of contrast, for code instantiated with an arbitrary structure there's no knowledge of field identifiers, so equivalent field access can only be achieved if that structure is directly exposed through the API.
I was curious with the MPL guys could see any use in having a typedef in a type_map for a mpl::vector, but it's pretty clear nobody reading this gives a hoot about "type_map"s as described, so I'll put a sock in it.
Are these things any different from the ArgumentPacks created by the Boost.Parameter library? -- Dave Abrahams Boost Consulting www.boost-consulting.com
participants (3)
-
David Abrahams
-
Larry Evans
-
Tony D