understanding how zip in range-v3 works - c++

I am trying to understand how ranges::views::zip works in range-v3. I understand that it is a range that allows to iterate on several ranges in one single loop by creating a tuple of the elements in different ranges.
std::vector<int> v1 = {0, 1, 2};
std::vector<char> v2 = {'a', 'b', 'c'};
auto zip = ranges::views::zip(v1,v2);
// zip(v1,v2) = [(0,a), (1,b), (2,c)]
ranges::actions::sort(zip);
std::sort(std::begin(zip), std::end(zip));
The sort using ranges::actions works fine but std::sort doesnt compile and gives the following error
/usr/include/c++/9.3.0/bits/stl_algobase.h:151: error: no matching function for call to ‘swap(concepts::return_t<ranges::common_pair<int&, double&>, void>, concepts::return_t<ranges::common_pair<int&, double&>, void>)’
151 | swap(*__a, *__b);
| ~~~~^~~~~~~~~~~~
Why is this happening?
I have also tried to remove elements in both containers at the same time. ranges::actions::unique doesn't compile with the following error:
/home/jjcasmar/projects/cpfsofaplugin/src/CPFSofaPlugin/minimalExample.cpp:27: error: no match for call to ‘(const ranges::actions::action_closure<ranges::actions::unique_fn>) (ranges::zip_view<ranges::ref_view<std::vector<int, std::allocator<int> > >, ranges::ref_view<std::vector<double, std::allocator<double> > > >&)’
27 | ranges::actions::unique(v1Andv2);
| ^
but auto lastIt = std::unique(std::begin(v1Andv2), std::end(v1Andv2)) compiles find, although I dont know how to get the internal iterators of the zip to be able to erase past the end elements.
I dont really understand how this works under the hood and why in some cases std algorithms work fine but in some cases it doesn't. Can someone give some explanation about this?

Look at the types:
auto zip = ranges::views::zip(v1, v2);
// ranges::zip_view<
// ranges::ref_view<std::vector<int>>
// ranges::ref_view<std::vector<char>>
// >
auto begin = std::begin(zip);
// ranges::basic_iterator<
// ranges::iter_zip_with_view<
// ranges::detail::indirect_zip_fn_,
// ranges::ref_view<std::vector<int>>,
// ranges::ref_view<std::vector<char>>
// >::cursor<false>
// >
using traits = std::iterator_traits<decltype(begin)>;
static_assert(std::is_same_v<traits::value_type, std::pair<int, char>>);
static_assert(std::is_same_v<traits::reference, ranges::common_pair<int&, char&>>);
The value_type type is a std::pair of values. The reference type is a ranges::common_pair of references.
std::sort uses std::iter_swap which is specified in terms of dereferencing the iterators and calling std::swap. So std::sort will try to swap two ranges::common_pair of references. On the other hand, ranges::actions::sort uses ranges::iter_swap which is customized to handle pairs and tuples of references.
Pairs and tuples of references are/were second class citizens in the standard library.
ranges::actions::unique requires an erasable range which evidently this does not satisfy.
Added
The documentation for range-v3 is sparse. To find information such as the above, there is of course looking at the source for range-v3, quick experimentation on godbolt.org (range-v3 is an available library), and "standard" C++ tricks to find the type of a variable (e.g., calling a function template which is declared but not defined, with the variable's type as the template argument, and seeing which instantiation is called).
To comment more on unique, ranges::action::unique does not return an iterator. It erases the non-unique elements and returns a range (see the source). In part of the compiler error which was omitted, the error references the fact that the range is not erasable (buried in a huge error).
ranges::unique returns an iterator and can be called without error. This is a basic_iterator<...>. One option is to use ranges::distance to find the distance from the begin zip iterator and use this to get the underlying iterator:
auto zip_uniq_iter = ranges::unique(zip);
auto first_uniq_iter = std::next(v1.begin(), ranges::distance(ranges::begin(zip), zip_uniq_iter));

You cannot use std::sort on views.
But you can transform your view to a vector and then it works:
https://godbolt.org/z/_FvCdD
I can recommend the following sites for more information on ranges:
https://www.walletfox.com/course/quickref_range_v3.php
https://mariusbancila.ro/blog/2019/01/20/cpp-code-samples-before-and-after-ranges/

Related

Providing an allocator for Boost's `cpp_dec_float_100`

I have a dataset stored in .root file format (from the CERN ROOT framework) as type cpp_dec_float_100 (from the boost::multiprecision library). This data is read into an std::vector<cpp_dec_float_100>. By default, cpp_dec_float_100 is unallocated. If I were to try to read this data into a vector as-is, an std::bad_alloc is thrown. So, I've taken the advice of the Boost docs and provided a generic allocator, which seems to solve the issue (and appears to cut the size the resulting vector in half).
Ultimately, I want to pass this vector as an argument to a function that I've written, which performs binary search on a vector to find the element of that vector closest to a given value:
#include <boost/multiprecision/cpp_dec_float.hpp>
using Mult_t = boost::multiprecision::cpp_dec_float<100, int, allocator<boost::multiprecision::number<boost::multiprecision::cpp_dec_float<100>>>>;
std::vector<Mult_t>::iterator search(std::vector<Mult_t> &vec, Mult_t value){
auto it = lower_bound(vec.begin(), vec.end(), value);
if(it != vec.begin()){
if(abs(value - *(it - 1)) < abs(value - *it)){
--it;
}
}
return it;
}
I'm using the "alias" Mult_t as the alternative is a bit of a mouthful.
So, given the vector vec and the value val, this finds the element in vec nearest to val.
If I use the cpp_dec_float_100 type as-is (i.e. Mult_t = boost::multiprecision::cpp_dec_float_100), this works great. However, when I attempt to provide an allocator, I'm given the error:
In module 'std' imported from input_line_1:1:
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../include/c++/4.8.5/bits/stl_algobase.h:965:18: error: invalid operands to binary expression ('boost::multiprecision::backends::cpp_dec_float<100, int,
std::allocator<boost::multiprecision::number<boost::multiprecision::backends::cpp_dec_float<100,
int, void>, boost::multiprecision::expression_template_option::et_on> > >' and 'const
boost::multiprecision::backends::cpp_dec_float<100, int,
std::allocator<boost::multiprecision::number<boost::multiprecision::backends::cpp_dec_float<100,
int, void>, boost::multiprecision::expression_template_option::et_on> > >')
if (*__middle < __val)
I don't quite understand what's going on here (I doubt seriously it has anything to do with the allocator), and the error message isn't terribly insightful.
Your problem has nothing to do with allocator, just because cpp_dec_float<...> has no operator<(), only number<cpp_dec_float<...>> supports.
You should redefine your Mult_t as:
using namespace boost::multiprecision;
using Mult_t = number<
cpp_dec_float<100, int, std::allocator<number<cpp_dec_float<100>>>>>;

How does the erase-remove idiom work with ranges/constrained algorithms?

I'm trying to use a c++20 constrained algorithm for the erase-remove idiom:
std::vector<int> v;
v.erase(std::unique(std::begin(v), std::end(v)), std::end(v));
but when I do a simple transformation:
v.erase(std::ranges::unique(v), std::end(v));
I get an error that the arguments to erase don't match:
error: no matching function for call to 'std::vector<int>::erase(std::ranges::borrowed_subrange_t<std::vector<int>&>, std::vector<int>::iterator)'
A similar error is produced if the second argument is std::ranges::end(v).
How can I get this to work?
The question originally used remove instead of unique, but there is an overloaded std::erase for all containers that makes that particular use case less motivating.
std::ranges::unique (and std::ranges::remove) returns a sub range from the first removed element to the end of the container so you need to use std::begin before passing to std::vector::erase:
v.erase(std::ranges::begin(std::ranges::remove(v, 42)), std::end(v));
v.erase(std::ranges::begin(std::ranges::unique(v)), std::end(v));
Another option would be decomposing the subrange returned by std::ranges::remove/unique, and use those iterators:
auto [Beg, End] = std::ranges::remove(v, 42);
v.erase(Beg, End);
It doesn't work since std::ranges::remove() returns not iterator but range. But even if you try v.erase(std::ranges::remove(...)) it will not work, because vector does not have erase() overload which takes range as parameter.
Instead, take a look at std::erase() (defined in <vector>). What you need is probably just std::erase(v, 42).

Does Fusion have a tail function?

I need a tail-like funciton that can be used like this:
boost::fusion::vector<char, int, float> a('a', 12, 5.5f);
boost::fusion::vector<int, float> b(12, 5.5f);
boost::fusion::copy( Tail(a), b );
In the documentation for Boost Fusion, there's a section under Algorithms called Transformation. The Functions listed here notably include one called pop_front. This seems to do exactly what we want:
Returns a new sequence, with the first element of the original removed.
...
Example
assert(pop_front(make_vector(1,2,3)) == make_vector(2,3));
For your example:
boost::fusion::vector<char, int, float> a('a', 12, 5.5f);
boost::fusion::vector<int, float> b(12, 5.5f);
boost::fusion::copy( boost::fusion::pop_front(a), b );
The name pop_front is a little strange, considering that it doesn't actually modify the input sequence, but returns a modified result. However, pop_front comes from the C++ standard library, where it is used for removing the first element of a collection such as with std::list::pop_front. Boost Fusion chose this name to be "more consistent" with the standard library.

How to store Iterators in a Map in C++ [duplicate]

Can I do normal computations with iterators, i.e. just increment it by adding a number?
As an example, if I want to remove the element vec[3], can I just do this:
std::vector<int> vec;
for(int i = 0; i < 5; ++i){
vec.push_back(i);
}
vec.erase(vec.begin() + 3); // removes vec[3] element
It works for me (g++), but I'm not sure if it is guaranteed to work.
It works if the iterator is a random access iterator, which vector's iterators are (see reference). The STL function std::advance can be used to advance a generic iterator, but since it doesn't return the iterator, I tend use + if available because it looks cleaner.
C++11 note
Now there is std::next and std::prev, which do return the iterator, so if you are working in template land you can use them to advance a generic iterator and still have clean code.
A subtle point is that the operator+ takes a Distance; i.e., a signed integer. If you increment the iterator by an unsigned, you may lose precision and run into a surprise. For example on a 64 bit system,
std::size_t n = (1 << 64) - 2;
std::vector<double> vec(1 << 64);
std::vector<double> slice(vec.begin() + n, vec.end());
leads to implementation-defined behavior. With g++ or clang, you can ask the compiler to warn you about such undesired conversions with the warning flag -Wsign-conversion that is not part of the canonical -Wall or -Wextra.
A work-around is to work on the pointer directly
std::vector<double> slice(vec.data() + n, vec.data() + vec.size());
It's not pretty but correct. In some occasions, you need to construct the iterator manually, for example
std::vector<double>::iterator fromHere{vec.data() + n};
vec.erase(fromHere, vec.end());
It works with random access iterators. In general you may want to look at std::advance which is more generic. Just be sure to understand performance implications of using this function template.
Number arithmetic is possible only with random access iterators such as those in std::vector and std::deque.
std::vector<int>list={1,2,3,4,5,6,7,8};
auto last_v=*(list.end()-1);
auto third_last_v=*(list.end()-3);
std::cout<<"Last & 3rd last entry for vector:"<<last_v<<","<<third_last_v<<std::endl;
will output 8 and 6, however for std::map, std::multimap, std::set, std::multiset having bidirectional iterator
std::map<int,std::string> map_={{1,"one"},{2,"two"},{3,"three"}};
auto last_mp=*(map_.end()-1);
auto third_last_mp=*(map_.end()-3);
std::cout<<"Last & 3rd last entry for map:("<<last_mp.first<<","<<last_mp.second<<") and ("<<third_last_mp.first<<","<<third_last_mp.second<<")"<<std::endl;
will result in
error: no match for ‘operator-’ (operand types are ‘std::map, int>::iterator {aka std::_Rb_tree_iterator, int> >}’ and ‘int’)
For bidirectional iterator std::next(),std::prev or std::advance() works
std::map<int,std::string> map_={{1,"one"},{2,"two"},{3,"three"}};
auto last_mp=*std::prev(map_.end(),1);
auto third_last_mp=*std::prev(map_.end(),3);
std::cout<<"Last & 3rd last entry for map:("<<last_mp.first<<","<<last_mp.second<<") and ("<<third_last_mp.first<<","<<third_last_mp.second<<")"<<std::endl;
will output (3,three) and (1,one). On a similar note std::unordered_map has a forward iterator, so here std::next() makes sense to use.

Difference in behaviour of list<int>::iterator and vector<int>::iterator

I have a std::list<int> and a std::vector<int>. I want to remove even elements from them, and duplicate odd element in them.
I've two different functions for both of them:
Vector:
std::vector<int> vec_remove_even_duplicate_odd(std::vector<int> target) {
std::vector<int>::iterator begin = target.begin();
while (begin != target.end()) {
if (*begin % 2 == 0) {
begin = target.erase(begin);
} else {
begin = target.insert(begin, *begin);
begin += 2;
}
}
return target;
}
This works fine. But the same function for std::list<int> shows error at the line begin += 2:
error: no match for ‘operator+=’ (operand types are ‘std::list<int>::iterator {aka std::_List_iterator<int>}’ and ‘int’)
If I change it to:
begin = begin + 2
it shows the following note:
note: mismatched types ‘const std::reverse_iterator<_Iterator>’ and ‘int’
But, if I change that line to:
++begin;
++begin;
It works fine for list too. So what is it with this behaviour, that I might have missed while reading about containers.
Why is the += operator not defined for std::list<T>::iterator? And why that message for simple + operator? I haven't even created a reverse_iterator?
I'm aware that a vector is a contiguous structure, while a list is not. But how will that matter, given that post-increment is applicable?
Is this issue specific to list only, or some other container also have this issue?
Since std::list is actually a linked list, its iterators provide only the functionality that is trivial to implement in such a data structure; in particular, std::list iterators are so-called bidirectional iterators, not random access iterators, thus they do not provide neither operator+= nor operator+, hence the messages you get.
If in a generic algorithm you need to go forward of n elements, regardless of the computational cost of the operation, you can use std::advance, which will use operator+= for random iterators and repeated application of ++ or -- in the other cases.
By the way, your loop for std::vector doesn't look fine - insertion and removal in a std::vector can invalidate iterators (including those you are using to iterate over your vector); you should change the approach of your algorithm (maybe the simplest thing is just to copy the elements in a separate vector).