Question

I am trying to return an iterator to the largest element in a filtered range. Here is what I have so far:

#include <boost/lambda/lambda.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <vector>
#include <iostream>

using namespace boost::adaptors;
using namespace boost::lambda;
using namespace std;

int main ()
{
  vector<double> x = {100, 150, 200, 110};
  auto it = boost::max_element(x | indexed(0) | filtered(_1>100)); /* problem here */
  cout << it.index() << endl;

  return 0;
}

I expected the code to print out the index in the vector x which has the largest element (ie 2), but unfortunately it does not compile (Linux 64bit, GCC 4.7.2), the problem being in the line indicated above. The first compilation error I get from the compiler (amongst others) is the following:

/boost/tuple/detail/tuple_basic.hpp:396:36: error: assignment of read-only member ‘boost::tuples::cons::head’

Any ideas what I am doing wrong? Or how else I can achieve what I am trying to do? Thanks in advance!

EDIT:

Changing the problematic line to :

auto it = boost::max_element<boost::return_found>(x | sliced(1,4) |   filtered(boost::function<bool(double)>(_1>100)));

seems to return the iterator to the largest element. However, is there a way to check that the iterator is within the range? Comparing it with boost::end(x) gives me an error. The only thing I can think of is to return

auto another_range = boost::max_element<boost::return_found_end>(x | sliced(1,4) |   filtered(boost::function<bool(double)>(_1>100)));

and check if boost::empty(another_range). Is this the only option? Thanks.

Was it helpful?

Solution

The specific error you've encountered appears because boost lambdas are not CopyAssignable. Here's a simpler way to achieve the same message:

auto f1 = _1 > 100;
auto f2 = f1;
f2 = f1; // same error

If you provide a CopyAssignable functor to filtered, boost.phoenix (which you should be using anyway, boost.lambda is on the road to deprecation in favor of phoenix), a hand-written struct, or the old faithful std::bind2nd(std::greater<double>(), 100), this line compiles with clang++:

bind2nd demo: http://liveworkspace.org/code/2xKZIf

phoenix demo: http://liveworkspace.org/code/18425g

It fails with gcc due to some boost.concept check, which is probably a bug, but it's a moot point because the result of filtered is boost::filtered_range, whose iterators don't have the .index() member function.

EDIT in response to comment: comparing iterator into filtered_range with the iterator into the original vector wouldn't work. However, since you used vector, and since it's still accessible, you can compare addresses, since neither indexed nor filtered make copies

#include <vector>
#include <iostream>
#include <cassert>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/phoenix.hpp>

using namespace boost::adaptors;
using namespace boost::phoenix::placeholders;

int main ()
{
    std::vector<double> x = {100, 150, 200, 110};
    auto it = boost::max_element( x | indexed(0) | filtered(arg1 < 110) );
    assert(&x[0] <= &*it && &*it < &x[0] + x.size());
    std::cout << "Element " << *it << " is at index " << &*it - &x[0] << '\n';
}

demo http://liveworkspace.org/code/1zBIJ9

Or, for a more general solution, you could transform your vector into a vector of pairs (when boost gets zip adaptor, it could be neatly zipped with counting_range), and carry the original sequence index along with the value through all the transformations.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top