Does boost have a datatype for set operations that is simpler than the STL?
I find the C++ STL method of doing simple set operations quite clunky to use. For example, to find the difference between two sets:
std::set<int> newUserIds;
set_difference(currentUserIds.begin(), currentUserIds.end(), mPreviousUserIds.begin(), mPreviousUserIds.end(), std::inserter(newUserIds, newUserIds.end()));
std::set<int> missingUserIds;
set_difference(mPreviousUserIds.begin(), mPreviousUserIds.end(), currentUserIds.begin(), currentUserIds.end(), std::inserter(missingUserIds, missingUserIds.end()));
mPreviousUserIds = currentUserIds;
Does boost offer an alternative set of classes that would reduce the above example to something like this:
set_type<int> newUserIds = currentUserIds.difference(mPreviousUserIds);
set_type<int> missingUserIds = mPreviousUserIds.difference(currentUserIds);
(Similar to QSet in Qt which overrides operator-
in this way.)
Solution 1:
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.
Solution 2:
See Boost Range Set algorithms. They still expect an output iterator though.