Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

set

stl

boost

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.)

like image 392
Tim MB Avatar asked Feb 26 '13 13:02

Tim MB


People also ask

What is set in STL?

Set is a standard template library (STL) container in C++, used in programming whenever we need to store unique elements (no duplicate values) and stored in a specifically sorted manner. The elements inside the set can be inserted or removed, but when inserted once, they cannot be modified.

Is set sorted in C++?

As mentioned above, sets in C++ are the type of STL containers that are used for storing elements in a sorted way. The operations allowed to be performed on sets are insertion and deletion. The elements are internally sorted according to a strict weak ordering in a set type container.

How do you traverse a set in C++?

Iterating over a set using iterator. In this method, an iterator itr is created and initialized using begin() function which will point to the first element, and after every iteration, itr points to the next element in a set and it will continue to iterate until it reaches the end of the set.

How do you access the elements of a set?

You cannot access items in a set by referring to an index or a key. But you can loop through the set items using a for loop, or ask if a specified value is present in a set, by using the in keyword.


3 Answers

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.

like image 117
Yakk - Adam Nevraumont Avatar answered Sep 20 '22 17:09

Yakk - Adam Nevraumont


See Boost Range Set algorithms. They still expect an output iterator though.

like image 12
Maxim Egorushkin Avatar answered Sep 21 '22 17:09

Maxim Egorushkin


No and I think it never have something like that, this is a general principle in C++ that when you can have a non-member function to do the job never make that function a member. so it can't be like that, but may be Boost::Range help you.

like image 3
BigBoss Avatar answered Sep 23 '22 17:09

BigBoss