Boost logo

Boost Users :

Subject: Re: [Boost-users] 'Hashable' concept for type erasure
From: Steven Watanabe (watanabesj_at_[hidden])
Date: 2014-03-24 16:16:08


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
with a reference type of [const] T&. (Warning,
this code is completely untested.)

In Christ,
Steven Watanabe

Boost-users list run by williamkempf at, kalb at, bjorn.karlsson at, gregod at, wekempf at