Does boost have a datatype for set operations that is simpler than the STL?

Nope. But I here is how to clean it up.

First, rewrite iterator based functions as ranged based functions. This halves your boilerplate.

Second, have them return container builders rather than take insert iterators: this gives you efficient assignment syntax.

Third, and probably too far, write them as named operators.

The final result is you get:

set<int> s = a *intersect* b;
set<int> s2 = c -difference- s;
set<int> s3 = a *_union_* (b *intersect* s -difference- s2);

… after writing a boatload of boilerplate code elsewhere.

As far as I know, boost does step 1.

But each of the above three stages should reduce your boilerplate significantly.

Container builder:

template<typename Functor>
struct container_builder {
  Functor f;
  template<typename Container, typename=typename std::enable_if<back_insertable<Container>::value>::type>
  operator Container() const {
    Container retval;
    using std::back_inserter;
    f( back_inserter(retval) );
    return retval;
  }
  container_builder(Functor const& f_):f(f_) {}
};

which requires writing is_back_insertable (pretty standard SFINAE).

You wrap your ranged based (or iterator based) functor that takes a back_insert_iterator as the last argument, and use std::bind to bind the input parameters leaving the last one free. Then pass that to container_builder, and return it.

container_builder can then be implicitly cast to any container that accepts std::back_inserter (or has its own ADL back_inserter), and move semantics on every std container makes the construct-then-return quite efficient.

Here is my dozen line named operator library:

namespace named_operator {
  template<class D>struct make_operator{make_operator(){}};

  template<class T, char, class O> struct half_apply { T&& lhs; };

  template<class Lhs, class Op>
  half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
    return {std::forward<Lhs>(lhs)};
  }

  template<class Lhs, class Op, class Rhs>
  auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
  -> decltype( named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
  {
    return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
  }
}

live example using it to implement vector *concat* vector. It only supports one operator, but extending it is easy. For serious use, I’d advise having a times function that by default calls invoke for *blah*, an add for +blah+ that does the same, etc. <blah> can directly call invoke.

Then the client programmer can overload an operator-specific overload and it works, or the general invoke.

Here is a similar library being used to implement *then* on both tuple-returning functions and futures.

Here is a primitive *in*:

namespace my_op {
  struct in_t:named_operator::make_operator<in_t>{};
  in_t in;

  template<class E, class C>
  bool named_invoke( E const& e, in_t, C const& container ) {
    using std::begin; using std::end;
    return std::find( begin(container), end(container), e ) != end(container);
  }
}
using my_op::in;

live example.

Leave a Comment