'Hashable' concept for type erasure
Hello, I've been trying to add a 'hashable' concept to one of my type erasure requirements, but I haven't been getting anywhere and I still don't understand the voluminous error messages enough to make any progress. Here's approximately what I tried so far: namespace mpl = boost::mpl; namespace te = boost::type_erasure; template<class T> struct hashable { static size_t apply(const T& obj) { return std::hash<T>()(obj); } }; namespace std { template<> class hash<t>{ public : size_t operator()(const test &t ) const { return te::call(hashable<test>(), t); } }; }; typedef mpl::vector< ... hashable<te::_self>, ...
requirements
typedef any<requirements> test; namespace std { template<> class hash<test>{ public : size_t operator()(const test&h ) const { return te::call(hashable<test>(), h); } }; }; I also tried something else, similar to the following: template<class C> struct hashable { static std::size_t apply(const C& c) { return c.hash(); } }; namespace boost { namespace type_erasure { template<class C, class Base> struct concept_interface<hashable<C>, Base, C> : Base { std::size_t hash() const { return call(hashable<C>(), *this); } }; } } // ... similar requirements / typedef as above namespace std { template<> class hash<test>{ public : size_t operator()(const test &t ) const { return t.hash(); } }; }; The first one is probably just wrong, and for the second one I'm getting some failed assertions when I try to use it with an implementation of hash that is: size_t hash() const { //coord is a member std::vector<int> return boost::hash_range(coord.begin(), coord.end()); } Any help would be most useful. -sc
AMDG On 03/20/2014 02:53 PM, Samuel Christie wrote:
I've been trying to add a 'hashable' concept to one of my type erasure requirements, but I haven't been getting anywhere and I still don't understand the voluminous error messages enough to make any progress.
I didn't run into any of the problems you described. Here's what I get by filling in your first version. (tested with gcc 4.7.2 -std=c++11) In Christ, Steven Watanabe
Yeah, I tested it some more myself and the second one actually worked in a limited test case; turns out the problem I was having was somewhere else. I am now using your solution for the first technique though; it seems a little better. Next problem I'm having though is using it with an erasure that can be used to store anything implementing insert and count as in a std::unordered_set. I've tried the following simple test, but it seems to have trouble converting between 'int' and the '_value' placeholder type. Again, thanks for all of your patient help. I've learned a lot. -sc On Thu, Mar 20, 2014 at 10:36 PM, Steven Watanabe <watanabesj@gmail.com>wrote:
AMDG
On 03/20/2014 02:53 PM, Samuel Christie wrote:
I've been trying to add a 'hashable' concept to one of my type erasure requirements, but I haven't been getting anywhere and I still don't understand the voluminous error messages enough to make any progress.
I didn't run into any of the problems you described. Here's what I get by filling in your first version. (tested with gcc 4.7.2 -std=c++11)
In Christ, Steven Watanabe
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
AMDG On 03/22/2014 04:10 PM, Samuel Christie wrote:
Next problem I'm having though is using it with an erasure that can be used to store anything implementing insert and count as in a std::unordered_set. I've tried the following simple test, but it seems to have trouble converting between 'int' and the '_value' placeholder type.
This is a bit tricky. I think, first you need to be very clear about exactly what you want. When you call history.count(1), the fact that history holds an unordered_set<int> has been lost. The library has no way to know at compile time the value_type is int. So, what do you want to happen if I write: history.count("a string"); a) Just don't do that. (i.e. undefined behavior) b) return 0; since history obviously doesn't contain any strings. c) An exception saying that you tried to pass the wrong type. d) Something else? In Christ, Steven Watanabe
Hmm. Maybe it would be best if I make a custom wrapper class anyway, then. All I really need is the ability to 1) record 'seen' objects in the history, and 2) be able to tell whether one of the same objects is in the history or not. Up until this point, I've been using unordered_sets, but I was hoping to support more sophisticated memory models that would only remember recent history, for instance. Of those choices, I think (b) makes the most sense for my purposes, though a or c would also be acceptable. I can't really see how I would achieve that with just type erasure though. This could easily be a case of an over-used hammer. If you don't mind my asking, which of them would you recommend, which do you think is achievable with type-erasure, and what other alternatives do you think I should be considering? -sc On Sat, Mar 22, 2014 at 7:48 PM, Steven Watanabe <watanabesj@gmail.com>wrote:
AMDG
On 03/22/2014 04:10 PM, Samuel Christie wrote:
Next problem I'm having though is using it with an erasure that can be used to store anything implementing insert and count as in a std::unordered_set. I've tried the following simple test, but it seems to have trouble converting between 'int' and the '_value' placeholder type.
This is a bit tricky. I think, first you need to be very clear about exactly what you want.
When you call history.count(1), the fact that history holds an unordered_set<int> has been lost. The library has no way to know at compile time the value_type is int. So, what do you want to happen if I write:
history.count("a string");
a) Just don't do that. (i.e. undefined behavior) b) return 0; since history obviously doesn't contain any strings. c) An exception saying that you tried to pass the wrong type. d) Something else?
In Christ, Steven Watanabe
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
AMDG On 03/22/2014 05:13 PM, Samuel Christie wrote:
Hmm. Maybe it would be best if I make a custom wrapper class anyway, then. All I really need is the ability to 1) record 'seen' objects in the history, and 2) be able to tell whether one of the same objects is in the history or not. Up until this point, I've been using unordered_sets, but I was hoping to support more sophisticated memory models that would only remember recent history, for instance.
Of those choices, I think (b) makes the most sense for my purposes, though a or c would also be acceptable. I can't really see how I would achieve that with just type erasure though. This could easily be a case of an over-used hammer.
Do you actually need to erase the value_type? If you can use has_count<size_t(int)>, then everything will just work.
If you don't mind my asking, which of them would you recommend, which do you think is achievable with type-erasure, and what other alternatives do you think I should be considering?
If it were just count, I would go for (b). However, that isn't an option for insert, since it's impossible to insert something that isn't an int into a container that stores ints. Whether consistency between the functions is more important than the extra convenience, only you can decide. Now that I think about it, all these options can all be implemented in basically the same way (you will need typeid_<_value> for this to work): template<class T> std::size_t generic_count(const any_set& c, const T& t) { typedef any<requirements, const _value&> val_ref; // check that the type of t matches the value_type // of the container. if(typeid_of<_value>(binding_of(c)) == typeid(t)) { // capture the value in a way that's compatible // with the container. val_ref v(t, binding_of(c)); return c.count(v); } else { return 0; // or throw or whatever } } You can put this directly in the concept_interface, if you define it manually instead of using BOOST_TYPE_ERASURE_MEMBER. In Christ, Steven Watanabe
Interesting. That looks useful, but at the same time I think I can actually get away without erasing _value. The actual type would be the erased type we were working on earlier, so I should be able to just stick it in explicitly. How would you erase insert() though? Its return type seems fairly complicated. I tried using std::pair<te::any<te::forward_iterator<> >, bool>, but I get "invalid initialization of reference of type 'int&' from expression of type 'const int'" for the simple test case. Thanks again, -sc On Sat, Mar 22, 2014 at 8:47 PM, Steven Watanabe <watanabesj@gmail.com>wrote:
AMDG
Hmm. Maybe it would be best if I make a custom wrapper class anyway,
All I really need is the ability to 1) record 'seen' objects in the history, and 2) be able to tell whether one of the same objects is in the history or not. Up until this point, I've been using unordered_sets, but I was hoping to support more sophisticated memory models that would only remember recent history, for instance.
Of those choices, I think (b) makes the most sense for my purposes,
On 03/22/2014 05:13 PM, Samuel Christie wrote: then. though
a or c would also be acceptable. I can't really see how I would achieve that with just type erasure though. This could easily be a case of an over-used hammer.
Do you actually need to erase the value_type? If you can use has_count<size_t(int)>, then everything will just work.
If you don't mind my asking, which of them would you recommend, which do you think is achievable with type-erasure, and what other alternatives do you think I should be considering?
If it were just count, I would go for (b). However, that isn't an option for insert, since it's impossible to insert something that isn't an int into a container that stores ints. Whether consistency between the functions is more important than the extra convenience, only you can decide. Now that I think about it, all these options can all be implemented in basically the same way (you will need typeid_<_value> for this to work):
template<class T> std::size_t generic_count(const any_set& c, const T& t) { typedef any<requirements, const _value&> val_ref; // check that the type of t matches the value_type // of the container. if(typeid_of<_value>(binding_of(c)) == typeid(t)) { // capture the value in a way that's compatible // with the container. val_ref v(t, binding_of(c)); return c.count(v); } else { return 0; // or throw or whatever } }
You can put this directly in the concept_interface, if you define it manually instead of using BOOST_TYPE_ERASURE_MEMBER.
In Christ, Steven Watanabe
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
AMDG On 03/24/2014 11:46 AM, Samuel Christie wrote:
Interesting. That looks useful, but at the same time I think I can actually get away without erasing _value. The actual type would be the erased type we were working on earlier, so I should be able to just stick it in explicitly.
How would you erase insert() though? Its return type seems fairly complicated. I tried using std::pair<te::any<te::forward_iterator<> >, bool>, but I get "invalid initialization of reference of type 'int&' from expression of type 'const int'" for the simple test case.
This is probably because unordered_set doesn't provide mutable iterators. You'll need to set the reference type of the iterator. Also if you create an independent any type for the iterator, you probably won't be able to do much with it, and you might as well return void. The iterator should really be another associated type, but that creates another problem. Handling composite return types like std::pair is painful. The library doesn't handle it because I don't have a good way to deal with it generically. The basic problem is that on the inside you have std::pair<std::unordered_set<int>::iterator, bool> and on the outside you have std::pair<any<requirements, _iter>, bool> and I need to convert from one to the other. Of course, it's fairly easy to do this for std::pair, but imagine instead trying to handle this for some arbitrary template with an unknown set of accessors and an unknown set of constructors. I'd need to have some kind of traits template that could be specialized to handle the conversion for specific types. If this were all, it would be manageable, but it gets worse. The library needs to convert from T (the stored type) to any<Concept, Placeholder>. The interface boundary inside the library doesn't know anything about T (this is the critical requirement for any kind of type erasure). It also doesn't know anything about Concept (This is necessary to allow composition of concept requirements). As a result the actual type passed through the dispatcher cannot be any<Concept, Placeholder> nor can it be T. It has to be void* (or something equivalent). So, given a template X<P>, which is used as the return type of an erased function, the actual conversions that we need are X<T> -> X<void*> -> X<any<C, P> >. This is now a problem, because there's absolutely no guarantee that X<void*> is a legal type. I don't have a good solution for this in the general case. It is possible to handle insert like this: typedef any<copy_constructible<> > internal_type; template<class C, class T, class I> struct has_insert { static std::pair<internal_type, bool> apply(C& c, const T& t) { // implicit conversion of std::pair should work, // but we need to make sure that we use the // specified iterator type regardless of what // c.insert actually returns. std::pair<I, bool> result = c.insert(t); return result; } }; namespace boost { namespace type_erasure { template<class C, class T, class I, class Base> struct concept_interface<has_insert<C, T, I>, Base, C> : Base { std::pair<typename rebind_any<Base, I>::type, bool> insert(typename as_param<Base, const T&>::type arg) { std::pair<internal_type, bool> result = call(has_insert<C, T, I>(), *this, arg); // Force the iterator to the correct any type. // We know that this is safe because the // iterator comes from has_insert::apply // which guarantees that the iterator // is the correct type for the placeholder I. typename rebind_any<Base, I>::type iter( result.first, binding_of(*this)); return std::make_pair(iter, result.second); }; }; }} Now you would just need to specify that I is a [forward|bidirectional|random_access]_iterator with a reference type of [const] T&. (Warning, this code is completely untested.) In Christ, Steven Watanabe
Maybe we could do something like your previous example that did some conversion and checking in the concept interface? I really only need to know whether or not it was already in the collection. I don't actually need the iterator for the matching item. Type erasure is definitely harder to set up than I initially presumed, and you have my admiration for being capable of writing the library. Debugging it seems particularly challenging. On Mar 24, 2014 4:12 PM, "Steven Watanabe" <watanabesj@gmail.com> wrote:
AMDG
On 03/24/2014 11:46 AM, Samuel Christie wrote:
Interesting. That looks useful, but at the same time I think I can actually get away without erasing _value. The actual type would be the erased type we were working on earlier, so I should be able to just stick it in explicitly.
How would you erase insert() though? Its return type seems fairly complicated. I tried using std::pair<te::any<te::forward_iterator<> >, bool>, but I get "invalid initialization of reference of type 'int&' from expression of type 'const int'" for the simple test case.
This is probably because unordered_set doesn't provide mutable iterators. You'll need to set the reference type of the iterator.
Also if you create an independent any type for the iterator, you probably won't be able to do much with it, and you might as well return void. The iterator should really be another associated type, but that creates another problem.
Handling composite return types like std::pair is painful. The library doesn't handle it because I don't have a good way to deal with it generically. The basic problem is that on the inside you have std::pair<std::unordered_set<int>::iterator, bool> and on the outside you have std::pair<any<requirements, _iter>, bool> and I need to convert from one to the other. Of course, it's fairly easy to do this for std::pair, but imagine instead trying to handle this for some arbitrary template with an unknown set of accessors and an unknown set of constructors. I'd need to have some kind of traits template that could be specialized to handle the conversion for specific types. If this were all, it would be manageable, but it gets worse.
The library needs to convert from T (the stored type) to any<Concept, Placeholder>. The interface boundary inside the library doesn't know anything about T (this is the critical requirement for any kind of type erasure). It also doesn't know anything about Concept (This is necessary to allow composition of concept requirements). As a result the actual type passed through the dispatcher cannot be any<Concept, Placeholder> nor can it be T. It has to be void* (or something equivalent).
So, given a template X<P>, which is used as the return type of an erased function, the actual conversions that we need are X<T> -> X<void*> -> X<any<C, P> >. This is now a problem, because there's absolutely no guarantee that X<void*> is a legal type. I don't have a good solution for this in the general case.
It is possible to handle insert like this:
typedef any<copy_constructible<> > internal_type; template<class C, class T, class I> struct has_insert { static std::pair<internal_type, bool> apply(C& c, const T& t) { // implicit conversion of std::pair should work, // but we need to make sure that we use the // specified iterator type regardless of what // c.insert actually returns. std::pair<I, bool> result = c.insert(t); return result; } };
namespace boost { namespace type_erasure { template<class C, class T, class I, class Base> struct concept_interface<has_insert<C, T, I>, Base, C> : Base { std::pair<typename rebind_any<Base, I>::type, bool> insert(typename as_param<Base, const T&>::type arg) { std::pair<internal_type, bool> result = call(has_insert<C, T, I>(), *this, arg); // Force the iterator to the correct any type. // We know that this is safe because the // iterator comes from has_insert::apply // which guarantees that the iterator // is the correct type for the placeholder I. typename rebind_any<Base, I>::type iter( result.first, binding_of(*this)); return std::make_pair(iter, result.second); }; }; }}
Now you would just need to specify that I is a [forward|bidirectional|random_access]_iterator with a reference type of [const] T&. (Warning, this code is completely untested.)
In Christ, Steven Watanabe
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
AMDG On 03/25/2014 10:04 AM, Samuel Christie wrote:
Maybe we could do something like your previous example that did some conversion and checking in the concept interface?
The code I posted at the end is somewhat similar to the code for handling the arguments, I suppose. You only need the conversion, not the checks, since the return value automatically has the correct type, modulo any bugs in the concept definition.
I really only need to know whether or not it was already in the collection. I don't actually need the iterator for the matching item.
Since you don't need the iterator, why don't you just return bool? template<class C, class T> struct has_insert { static bool apply(C& c, const T& t) { return c.insert(t).second; } }; // basic concept_interface In Christ, Steven Watanabe
participants (2)
-
Samuel Christie
-
Steven Watanabe