
AMDG On 06/15/2012 07:19 PM, Daniel Larimer wrote:
On Jun 15, 2012, at 10:36 AM, Hite, Christopher wrote:
Daniel Larimer:
I suspect that you could achieve generic 'value semantics' for any pure virtual interface via a very simple macro + other C++11 features. I think the cost of making a call with the following code would be the same as using pointer semantics via a virtual call. Wow thanks for proving my point. So value<T> requires T to be copyable.
Why do you use virtual inheritance? It does cost something, I think a extra word in the object which gets added to this before calling stuff on the interface.
I have updated my example and taken it a bit beyond proto-type code. In the example below I demonstrate interface 'composition', added operator (ostream,+,etc) support and completely eliminated the need for a macro to make things work. I also demonstrated overloading operator() to create a 'boost function' type erasure. It will now store 'non class types' as it no longer inherits from the stored type so it could store non-class functions as well. With a bit more work I think I could have any< function<Signature> > working. The code no longer depends on variadic templates and should compile with VC2010.
Virtual inheritance is needed to solve diamond inheritance problem and to support interface composition (you only want one instance to actually hold the data)
I think that the header (any.hpp) just about covers value semantics. A slightly modified version could provide pointer and reference semantics. Pointer and reference semantics would not need to heap allocate and I believe I can avoid heap allocation for all 'small' types with a little bit of template logic.
https://github.com/bytemaster/mace/blob/master/libs/stub/include/mace/stub/a...
https://github.com/bytemaster/mace/blob/master/libs/stub/include/mace/stub/a...
If I went back to the MACRO based approach combined with variadic templates I could eliminate one virtual indirection, but I don't know if that is enough to justify loss of portability and the use of a MACRO. I suppose there is room for both with/without a MACRO for 'fast' versions.
With my new header I don't think I would ever use the TypeErasure library under consideration because of its three fatal flaws:
1) Lack of readability... I could hardly follow the examples and without documentation your typical coder would never be able to follow the code or know what interface they need to expose.
2) Lack of writability... Not only is the TypeErasure library hard to read, it is hard to figure out how to use it to do what you want.
Okay. Consider: template<class T = _self> struct member_add1 { static void apply(T& t, int i) { t.add(i); } }; template<class T = _self> struct member_add2 { static void apply(T& t, double d, std::string s) { t.add(d, s); } }; template<class T = _self> struct member_sub { static int apply(T& t, int i) { return t.sub(i); } }; namespace boost { namespace type_erasure { template<class T, class Base> struct concept_interface<member_sub<T>, Base, T> : Base { int sub(int i) { return call(member_sub<T>(), *this, i); } }; }} template<class T = _self> struct my_interface : mpl::vector< member_sub<T>, member_add1<T>, member_add2<T>, ostreamable<T>, callable<int(int), T> > {}; namespace boost { namespace type_erasure { template<class T, class Base> struct concept_interface<my_interface<T>, Base, T> : Base { int add(int i) { return call(member_add1<T>(), *this, i); } int add(double d, std::string s) { return call(member_add2<T>(), *this, d, s); } }; }} This is about the same amount of code as your example. What do you have to keep track of here: - primitive concept defs - Use of the placeholder _self - The static apply function - concept_interface: - Inheritance from Base is required - The argument of the concept must match the placeholder - boost::type_erasure::call - Use of MPL Sequences to combine concepts With your code: - virtual inheritance - Use of forward_interface - inheritance from any_store<T> - The implementation is a specialization of the interface - this->val With some macros my code becomes. (This macro is fairly easy to write, since it's just a slightly more generic version of the macros I used to define my own operators). BOOST_TYPE_ERASURE_MEMBER((member_add1), add, 1); BOOST_TYPE_ERASURE_MEMBER((member_add2), add, 2); BOOST_TYPE_ERASURE_MEMBER((member_sub), sub, 1); template<class T = _self> struct sub_inter : mpl::vector<member_sub<T> > {}; template<class T = _self> struct my_interface : mpl::vector< member_sub<T, int>, member_add1<T, int>, member_add2<T, double, std::string>, ostreamable<T>, callable<int(int), T> > {};
<snip> So perhaps we should change the nature of the discussion on this library from
"should we even have or use such a tool?"
to
"If you are going to use such a technique, what is the best library for the job?"
If there is enough interest and we can establish some more through requirements, I would gladly polish up my any<> interface to complete a simple and small library for doing this.
Here's why I believe that my library is superior: - You don't have a good way to represent boost::type_erasure::equality_comparable<>, and you have no way to represent equality_comparable<_a, _b>. The former is critical, the latter less so. (Concepts involving multiple types or associated types appear all the time in generic programming. Try implementing a type erased version of your favorite STL algorithm). - The type erased ostream operator in your example has it's arguments backwards. Doing it right adds extra complexity. - Virtual inheritance makes your objects bigger and increases the cost of dispatching. - You can't beat a macro interface for keeping the base concept definitions simple. In Christ, Steven Watanabe