2

I am trying to generalize a function I have which used to take two iterators to a vector of a specific data-structure, and re-arrange the elements in a certain way using std::iter_swap (like std::sort does).

Since this function only actually needs a subset of the data, and I will need to use it in other contexts in the future, I thought about removing the dependency on the data structure, and use boost::transform_iterator at the point of call to handle the transformation.

Unfortunately, it seems that boost::transform_iterator is not happy with this change. I can imagine why: std::iter_swap is usually implemented as std::swap(*lhs, *rhs), and dereferencing the transform_iterator does not yield the original element to swap in the correct way.

I was wondering if there was a way to handle this case. I am open to use boost::range or the experimental std::ranges ts if it needed.

This question is probably similar to this one, but even there the solution ends up modifying the subset of data the algorithm needs, rather than the outside structure.

Here is an MWE:

#include <boost/iterator/transform_iterator.hpp>
#include <vector>
#include <algorithm>
#include <iostream>

struct A {
    int x;
    int y;
};

template <typename It>
void my_invert(It begin, It end) {
    while (begin < end) {
        std::iter_swap(begin++, --end);
    }
}

template <typename It>
void my_print(It begin, It end) {
    for (; begin != end; ++begin)
        std::cout << (*begin) << ' ';
    std::cout << '\n';
}

int main() {
    std::vector<int> x{7,6,5,4,3,2};

    my_invert(std::begin(x), std::end(x));
    my_print(std::begin(x), std::end(x));

    auto unwrap = +[](const A & a) { return a.x; };

    std::vector<A> y{{9,8}, {7,6}, {5,4}, {3,2}};

    auto begin = boost::make_transform_iterator(std::begin(y), unwrap);
    auto end = boost::make_transform_iterator(std::end(y), unwrap);

    //my_invert(begin, end); // Does not work.
    my_print(begin, end);

    return 0;
}
Svalorzen
  • 5,353
  • 3
  • 30
  • 54

2 Answers2

2

Accessing the underlying iterator

You could access the base() property of transform_iterator (inherited publicly from iterator_adaptor) to implement your custom transform_iter_swap, for swapping the underlying data of the wrapped iterator.

E.g.:

template<class IteratorAdaptor>
void transform_iter_swap(IteratorAdaptor a, IteratorAdaptor b)
{
   std::swap(*a.base(), *b.base());
}

template <typename It>
void my_invert(It begin, It end) {
    while (begin < end) {
        transform_iter_swap(begin++, --end);
    }
}

After which your example (omitting the std::vector part) runs as expected:

my_invert(begin, end); // OK
my_print(begin, end);  // 3 5 7 9

If you want a general function template to cover both the boost (adaptor) iterators as well as typical iterators, you could e.g. use if constexpr (C++17) based on whether the iterators public typedef iterator_category derives from boost::iterators::no_traversal_tag or not:

// expand includes with
#include <boost/iterator/iterator_categories.hpp>

template <class It>
void iter_swap(It a, It b) {
  if constexpr(std::is_base_of<
        boost::iterators::no_traversal_tag,
        typename It::iterator_category>::value) {
      std::swap(*a.base(), *b.base());
    }
  else {
    std::swap(*a, *b);
  }
}

template <typename It>
void my_invert(It begin, It end) {
    while (begin < end) {
        iter_swap(begin++, --end);
    }
}
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • This makes sense, and I did think about this. The only problem I see is that basically forces me to use transform iterators in `my_invert` even if I don't need them (for example for the first vector). I could make it a separate implementation (something like `my_invert_transformed`), but it would be nice if there was a way to keep the method general. – Svalorzen Aug 25 '18 at 23:32
  • 1
    You could leverage SFINAE to chose between "real iterators" and e.g. special ones (such as boosts `transform_iterator`): I could add an example. – dfrib Aug 25 '18 at 23:34
  • Maybe I could use an `if constexpr` to automatically call `base()` if it exists, but it looks a bit like a hack. – Svalorzen Aug 25 '18 at 23:34
  • Another idea I am looking into is to specialize `std::iter_swap` for the boost iterators so that they do the right thing.. – Svalorzen Aug 25 '18 at 23:36
  • @Svalorzen I think it would be better to leverage whether we are working with boosts iterator types or not, rather than existance of an arbitary method (`base()`). Regarding specializing `std::iter_swap`: I usually take very much case before chaninging, even by specialization, the behaviour of std lib constructs. – dfrib Aug 25 '18 at 23:36
  • I know, I'm mostly just trying to evaluate all possible venues of approach so that I can actually choose the one that I like the most. I'm also trying to look into boost::range and the ranges ts since I'm not very familiar with them. Maybe the allow to do this kind of stuff? – Svalorzen Aug 25 '18 at 23:46
  • 1
    @Svalorzen I haven't used `boost:range` myself, but maybe that's an option. I expanded my answer with one (C++17) approach for when to swap based on `base()` or not. – dfrib Aug 26 '18 at 00:02
1

The problem comes from the unary predicate you've passed. Notice, that since you allow the return type to be deduced, the return type is deduced to be an int, a copy is returned, and the compilation fails when you try to swap two unmodifiable ints. However, if you were to specify the return type to be int&, like so:

 auto unwrap = [](A & a)->int& { return a.x; }; // explicitly demand to return ref

It will compile, and reverse the elements. Tested on gcc 8.1.0 and clang 6.0.0.

paler123
  • 976
  • 6
  • 18
  • Thanks, this indeed can be one of the problems. However notice that in your solution only `a.x` gets reverted. What I would like is that the original elements get reverted. As in, `{9, 8}` should be last, but not be changed to `{3,8}`. – Svalorzen Aug 25 '18 at 23:10
  • Well, I doubt that you can get it to work, and honestly can't see a reason why you would want it to work- you can still reverse the elements using just the normal iterators of the vector, and e.g. only print with the transform_iterator. – paler123 Aug 25 '18 at 23:15
  • The code I have posted is an example. I am not printing the data. I am doing complex transformations on them, and the swaps are conditional on the data I am feeding to the function. Thus no, I cannot reverse the element using the normal iterators. – Svalorzen Aug 25 '18 at 23:23