1

I want to get the unique elements from a vector<foo> based on a member of foo. I am using boost::adaptors::transform to select the member, then sorting, then using boost::adaptors::unique. I'm having trouble getting the sort step to work. Leaving aside the unique call for now, I've tried the below code on Coliru.

#include <iostream>
#include <string>
#include <vector>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/sort.hpp>

struct foo
{
    foo(std::string a) : bar(a) {}

    std::string bar;
    bool operator<(const foo& rhs) const {return bar < rhs.bar;}
};

int main()
{
    std::vector<foo> words = { foo("z"), foo("d"), foo("b"), foo("c") };

    #if 1
    {
        auto asString = boost::adaptors::transform(words, +[](const foo& x) {return x.bar;});
        auto sortedStrings = boost::range::sort(asString);
        for (const auto& el : sortedStrings)
            std::cout << el << std::endl;
    }
    #else
    {
        auto asString = boost::adaptors::transform(words, +[](const foo& x) {return x.bar;});
        std::sort(asString.begin().base(), asString.end().base());
        for (const auto& el : asString)
            std::cout << el << std::endl;
    }

    {
        auto sortedStrings = boost::range::sort(words);
        for (const auto& el : sortedStrings)
            std::cout << el.bar << std::endl;
    }
    #endif


    return 0;
}

The #if section doesn't work:

In file included from /usr/local/include/c++/6.3.0/bits/char_traits.h:39:0,
                 from /usr/local/include/c++/6.3.0/ios:40,
                 from /usr/local/include/c++/6.3.0/ostream:38,
                 from /usr/local/include/c++/6.3.0/iostream:39,
                 from main.cpp:1:
/usr/local/include/c++/6.3.0/bits/stl_algobase.h: In instantiation of 'void std::iter_swap(_ForwardIterator1, _ForwardIterator2) [with _ForwardIterator1 = boost::iterators::transform_iterator<std::__cxx11::basic_string<char> (*)(const foo&), __gnu_cxx::__normal_iterator<foo*, std::vector<foo> >, boost::iterators::use_default, boost::iterators::use_default>; _ForwardIterator2 = boost::iterators::transform_iterator<std::__cxx11::basic_string<char> (*)(const foo&), __gnu_cxx::__normal_iterator<foo*, std::vector<foo> >, boost::iterators::use_default, boost::iterators::use_default>]':
/usr/local/include/c++/6.3.0/bits/stl_algo.h:84:20:   required from 'void std::__move_median_to_first(_Iterator, _Iterator, _Iterator, _Iterator, _Compare) [with _Iterator = boost::iterators::transform_iterator<std::__cxx11::basic_string<char> (*)(const foo&), __gnu_cxx::__normal_iterator<foo*, std::vector<foo> >, boost::iterators::use_default, boost::iterators::use_default>; _Compare = __gnu_cxx::__ops::_Iter_less_iter]'
/usr/local/include/c++/6.3.0/bits/stl_algo.h:1918:34:   required from '_RandomAccessIterator std::__unguarded_partition_pivot(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = boost::iterators::transform_iterator<std::__cxx11::basic_string<char> (*)(const foo&), __gnu_cxx::__normal_iterator<foo*, std::vector<foo> >, boost::iterators::use_default, boost::iterators::use_default>; _Compare = __gnu_cxx::__ops::_Iter_less_iter]'
/usr/local/include/c++/6.3.0/bits/stl_algo.h:1950:38:   required from 'void std::__introsort_loop(_RandomAccessIterator, _RandomAccessIterator, _Size, _Compare) [with _RandomAccessIterator = boost::iterators::transform_iterator<std::__cxx11::basic_string<char> (*)(const foo&), __gnu_cxx::__normal_iterator<foo*, std::vector<foo> >, boost::iterators::use_default, boost::iterators::use_default>; _Size = long int; _Compare = __gnu_cxx::__ops::_Iter_less_iter]'
/usr/local/include/c++/6.3.0/bits/stl_algo.h:1965:25:   required from 'void std::__sort(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = boost::iterators::transform_iterator<std::__cxx11::basic_string<char> (*)(const foo&), __gnu_cxx::__normal_iterator<foo*, std::vector<foo> >, boost::iterators::use_default, boost::iterators::use_default>; _Compare = __gnu_cxx::__ops::_Iter_less_iter]'
/usr/local/include/c++/6.3.0/bits/stl_algo.h:4707:18:   required from 'void std::sort(_RAIter, _RAIter) [with _RAIter = boost::iterators::transform_iterator<std::__cxx11::basic_string<char> (*)(const foo&), __gnu_cxx::__normal_iterator<foo*, std::vector<foo> >, boost::iterators::use_default, boost::iterators::use_default>]'
/usr/local/include/boost/range/algorithm/sort.hpp:33:14:   required from 'RandomAccessRange& boost::range::sort(RandomAccessRange&) [with RandomAccessRange = boost::range_detail::transformed_range<std::__cxx11::basic_string<char> (*)(const foo&), std::vector<foo> >]'
main.cpp:27:57:   required from here
/usr/local/include/c++/6.3.0/bits/stl_algobase.h:148:11: error: no matching function for call to 'swap(boost::iterators::detail::iterator_facade_base<boost::iterators::transform_iterator<std::__cxx11::basic_string<char> (*)(const foo&), __gnu_cxx::__normal_iterator<foo*, std::vector<foo> >, boost::iterators::use_default, boost::iterators::use_default>, std::__cxx11::basic_string<char>, boost::iterators::random_access_traversal_tag, std::__cxx11::basic_string<char>, long int, false, false>::reference, boost::iterators::detail::iterator_facade_base<boost::iterators::transform_iterator<std::__cxx11::basic_string<char> (*)(const foo&), __gnu_cxx::__normal_iterator<foo*, std::vector<foo> >, boost::iterators::use_default, boost::iterators::use_default>, std::__cxx11::basic_string<char>, boost::iterators::random_access_traversal_tag, std::__cxx11::basic_string<char>, long int, false, false>::reference)'
       swap(*__a, *__b);

Whereas the std::sort with the base iterator type in the #else section does work. I don't understand why. In my real use case, I am also using boost::adaptors::indirect and boost::adaptors::filter (and I'd prefer to do the filtering before doing the sorting, or at least try to do so and see how it performs), so that's why I'm not simply sorting words with a lambda predicate before doing the transform.

Matt Chambers
  • 2,229
  • 1
  • 25
  • 43

1 Answers1

2

Problem is that you have a view of temporary strings that you cannot swap. you may fix you code with the following:

auto asString = boost::adaptors::transform(words, +[](foo& x) -> std::string& {return x.bar;});

Demo

Note that with that, you sort the string directly, and not the class.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Ah, that makes sense that `const foo& x` can't return a non-const reference to `bar`. Is the explicit `std::string&` necessary? It will return by value by default? In my real use case I discovered the const issue for a different reason. My real use case is making `boost::adaptors::unique` work on a vector of iterators into a set, and of course the strings in the set can't be swapped, so the `.base()` approach can't work. Now I take a copy of the iterators sort/unique on that. – Matt Chambers Mar 13 '17 at 19:49
  • @MattChambers: care that `sort` also requires random accessor iterators, so cannot be used with `filter` or `unique` view. – Jarod42 Mar 13 '17 at 20:04
  • 1
    This is spiffy, but highly unlikely to be what people need, IYAM: http://coliru.stacked-crooked.com/a/31653bbd316a795b – sehe Mar 13 '17 at 20:17
  • I have to `sort` it before my `unique` will do its job; I found that out the hard way. :) But yeah, I misread the filter documentation before; it would return a Bidirectional Range when given a random access range. So I would do transform -> sort -> filter -> unique. – Matt Chambers Mar 13 '17 at 20:17
  • You tease me with your fancy generic lambdas. I'm using VS2013 for now. :P Which, infuriatingly, also does not support the + prefix in a sane way. – Matt Chambers Mar 13 '17 at 20:30