Summary
I have a custom array class:
template<typename T, int SIZE>
class Array {
private:
T mArray[SIZE];
};
To enable support for std algorithms, with ranges, I want to create an iterator for this class. It would seem that std::contiguous_iterator would be the optimal choice since I can guarantee contiguous memory layout for the data. Following the iterator tutorial I should create a class inside this class. However, I should somehow be (quoted) "For example, instead of the std::forward_iterator_tag tag you would mark your iterator with the std::forward_iterator concept.".
I have a hard time figuring out what the syntax would look like for this, and I have been unable to find a post on the web showcasing this.
Question
How do I complete the following code snippet to implement std::contiguous_iterator for my Array<T,S> class?:
import <iterator>;
template<typename T, int SIZE>
class Array {
public:
const T& operator[](int i) { return mArray[i]; }
T& operator[](int i) { return mArray[i]; }
private:
T mArray[SIZE];
public:
struct Iterator {
Iterator(T* ptr) : mPtr(ptr) {}
private:
T* mPtr;
};
Iterator begin() { return Iterator(&mArray[0]); }
Iterator end() { return Iterator(&mArray[SIZE]); }
};
NOTE: There is a lot of operator overloads. An answer is not required to provide all of them. I just need an example syntax for where to place the concept, then I can probably figure out the rest.
As far as I could tell you need typedefs on the iterator class, so simply using a pointer was not sufficient. Here is an example:
#include <iterator>
#include <algorithm>
template<typename T, int SIZE>
class Array {
public:
const T& operator[](int i) const { return mArray[i]; }
T& operator[](int i) { return mArray[i]; }
private:
T mArray[SIZE];
public:
struct iterator
{
using difference_type=std::ptrdiff_t;
using value_type=std::remove_cv_t<T>;
using pointer=T*;
using reference=T&;
using iterator_category=std::random_access_iterator_tag;
using iterator_concept=std::contiguous_iterator_tag;
using self_type=iterator;
iterator(T *x) : ptr(x) {}
T operator*() { return *ptr; }
T operator->() { return ptr; }
difference_type operator-(const iterator& rhs) { return ptr-rhs.ptr; }
iterator& operator ++() { ++ptr; return *this;}
bool operator !=(const iterator& rhs) { return ptr != rhs.ptr; }
private:
T * ptr;
};
iterator begin() { return &mArray[0]; }
iterator end() { return &mArray[SIZE]; }
};
int foo(Array<int, 7>& a)
{
int sum;
for (auto x : a)
{
sum+=x;
}
return sum;
}
int goo(Array<int, 7>& a, int x)
{
auto ret=std::find(a.begin(), a.end(), x);
if (ret!=a.end()) return *ret;
return 0;
}
Note that you would likely need const_iterator and reverse_iterators for const and non-const ...
Credits
Thanks to #glenn-teitelbaum for pointing me in the right direction. I think I managed to figure out how to do this. It took a long time, so this will hopefully save someone else that trouble.
[Answering my own question]
The iterator should comply with the std::contiguous_iterator concept, so I looked at cppreference for the necessary parts. The concept is defined like this:
template<class I>
concept contiguous_iterator =
std::random_access_iterator<I> &&
std::derived_from</*ITER_CONCEPT*/<I>, std::contiguous_iterator_tag> &&
std::is_lvalue_reference_v<std::iter_reference_t<I>> &&
std::same_as<
std::iter_value_t<I>, std::remove_cvref_t<std::iter_reference_t<I>>
> &&
requires(const I& i) {
{ std::to_address(i) } ->
std::same_as<std::add_pointer_t<std::iter_reference_t<I>>>;
};
So in order to implement this concept, I must first also implement std::random_access_iterator, std::is_derived_from<[...]>, std::is_lvalue_reference_v<[...]>, and so on. This is a recursive process, especially since std::random_access_iterator builds on top of 4 other iterator types. Since this is a C++20 question, I aim to use C++20 features as much as possible (since it greatly simplifies the implementation). This is the complete iterator implementation:
template<typename T, int SIZE>
class Array
{
public:
class Iterator
{
public:
using iterator_category = std::contiguous_iterator_tag;
using iterator_concept = std::contiguous_iterator_tag;
//using difference_type = std::ptrdiff_t; // Likely the same
using difference_type = typename std::iterator<
std::contiguous_iterator_tag, T>::difference_type;
//using value_type = T;
using value_type = std::remove_cv_t<T>; // Using `T` seems sufficient
using pointer = T*;
using reference = T&;
// constructor for Array<T,S>::begin() and Array<T,S>::end()
Iterator(pointer ptr) : mPtr(ptr) {}
// std::weakly_incrementable<I>
Iterator& operator++() { ++mPtr; return *this; }
Iterator operator++(int) { Iterator tmp = *this; ++(*this); return tmp; }
Iterator() : mPtr(nullptr/*&mArray[0]*/) {} // TODO: Unsure which is correct!
// std::input_or_output_iterator<I>
reference operator*() { return *mPtr; }
// std::indirectly_readable<I>
friend reference operator*(const Iterator& it) { return *(it.mPtr); }
// std::input_iterator<I>
// No actions were needed here!
// std::forward_iterator<I>
// In C++20, 'operator==' implies 'operator!='
bool operator==(const Iterator& it) const { return mPtr == it.mPtr; }
// std::bidirectional_iterator<I>
Iterator& operator--() { --mPtr; return *this; }
Iterator operator--(int) { Iterator tmp = *this; --(*this); return tmp; }
// std::random_access_iterator<I>
// std::totally_ordered<I>
std::weak_ordering operator<=>(const Iterator& it) const {
return std::compare_three_way{}(mPtr, it.mPtr);
// alternatively: `return mPtr <=> it.mPtr;`
}
// std::sized_sentinel_for<I, I>
difference_type operator-(const Iterator& it) const { return mPtr - it.mPtr; }
// std::iter_difference<I> operators
Iterator& operator+=(difference_type diff) { mPtr += diff; return *this; }
Iterator& operator-=(difference_type diff) { mPtr -= diff; return *this; }
Iterator operator+(difference_type diff) const { return Iterator(mPtr + diff); }
Iterator operator-(difference_type diff) const { return Iterator(mPtr - diff); }
friend Iterator operator+(difference_type diff, const Iterator& it) {
return it + diff;
}
friend Iterator operator-(difference_type diff, const Iterator& it) {
return it - diff;
}
reference operator[](difference_type diff) const { return mPtr[diff]; }
// std::contiguous_iterator<I>
pointer operator->() const { return mPtr; }
using element_type = T;
private:
T* mPtr;
};
// === STATIC ASSERTS ===
// - to verify correct Iterator implementation!
static_assert(std::weakly_incrementable<Iterator>);
static_assert(std::input_or_output_iterator<Iterator>);
static_assert(std::indirectly_readable<Iterator>);
static_assert(std::input_iterator<Iterator>);
static_assert(std::incrementable<Iterator>);
static_assert(std::forward_iterator<Iterator>);
static_assert(std::bidirectional_iterator<Iterator>);
static_assert(std::totally_ordered<Iterator>);
static_assert(std::sized_sentinel_for<Iterator, Iterator>);
static_assert(std::random_access_iterator<Iterator>);
static_assert(std::is_lvalue_reference_v<std::iter_reference_t<Iterator>>);
static_assert(std::same_as<std::iter_value_t<Iterator>,
std::remove_cvref_t<std::iter_reference_t<Iterator>>>);
static_assert(std::contiguous_iterator<Iterator>);
const T& operator[](int i) const {
if (i < 0 || i >= SIZE) {
throw std::runtime_error("Array index out of bounds");
}
return mArray[i];
}
T& operator[](int i) {
if (i < 0 || i >= SIZE) {
throw std::runtime_error("Array index out of bounds");
}
return mArray[i];
}
Iterator begin() { return Iterator(&mArray[0]); }
Iterator end() { return Iterator(&mArray[SIZE]); }
private:
T mArray[SIZE];
};
// Check that the Array class can be used as a contiguous_range.
static_assert(std::ranges::contiguous_range<Array<int, 10>>);
NOTE: using element_type = T; was necessary because of a bug in the specification, which might be fixed. I found information about that here. Adding this fixed issue with std::to_address<Iterator> not being able to compile, and was the last missing piece in going from std::random_access_iterator to std::contiguous_iterator.
Testing
I did not perform a complete testing suite with all algorithms, but I chose a few which depend on ranges and std::random_access_iterator. It all runs smoothly. I also depend on building standard library headers as module units, because I want to showcase how C++20 features work together.
import <stdexcept>;
import <iostream>;
import <iterator>;
import <algorithm>;
import <random>;
#include <memory> // fails to build header unit!
template<typename T, int SIZE>
class Array
{
[...]
};
int main()
{
Array<int, 10> arr;
for (int i = 0; i < 10; i++) arr[i] = i;
// I need to call std::ragnes::shuffle since that depends on
// std::random_access_iterator, so that is a minimum.
// https://en.cppreference.com/w/cpp/algorithm/ranges/shuffle
std::random_device rd;
std::mt19937 gen{rd()};
std::cout << "before random shuffle:\n";
for (auto& i : arr) std::cout << i << ' ';
std::ranges::shuffle(arr, gen);
std::cout << "\nafter random shuffle:\n";
for (auto& i : arr) std::cout << i << ' ';
std::cout << '\n';
// Also std::ranges::stable_sort is a good check (also random_access_iterator):
// https://en.cppreference.com/w/cpp/algorithm/ranges/stable_sort
std::cout << "after stable_sort:\n";
std::ranges::stable_sort(arr);
for (auto& i : arr) std::cout << i << ' ';
std::cout << '\n';
auto [min,max] = std::ranges::minmax(arr);
std::cout << "min: " << min << ", max: " << max << '\n';
return 0;
}
Related
I have a vector that looks like this:
std::vector<std::vector<MyClass>> myVector;
And I would like to access its elements through iterators as if it was an unidimensional vector:
for (auto& x : myVector)
{
foo(x); // x is an object of type MyClass
}
(i.e. the fact that there are multiple dimensions is transparent to whoever loops through myVector)
I have an idea of how this should be done, have a custom iterator implementation that saves current indexes so that when one of the vectors has no more elements, it resets one of the indexes and increments the other so that it can start iterating through the next vector and so on. But I have been trying to code this idea but can't seem to get this working. Does anyone have any idea of how I can possibly achieve this? Or even better, if there's any open-source project that has a similar implementation?
Thanks.
It's totally possible to define your own iterator to hide all the details of iterating through a vector of vector, I wrote some code to give you the idea, mind that it should require more checks but it basically works and give you the idea.
You just need to write the required operations to make it work in other code like an opaque iterator.
template <typename T>
struct vector2d
{
public:
class iterator
{
public:
using vector_type = std::vector<std::vector<T>>;
using first_level_iterator = typename std::vector<std::vector<T>>::iterator;
using second_level_iterator = typename std::vector<T>::iterator;
private:
vector_type& data;
first_level_iterator fit;
second_level_iterator sit;
public:
iterator(vector_type& data, bool begin) : data(data)
{
if (begin)
{
fit = data.begin();
sit = fit->begin();
}
else
{
fit = data.end();
}
}
inline bool operator!=(const iterator& other) const { return fit != other.fit || (fit != data.end() && sit != other.sit); }
inline const iterator& operator++() {
// don't go past end
if (fit == data.end())
return *this;
// increment inner iterator
++sit;
// if we reached the end of inner vector
if (sit == fit->end())
{
// go to next vector
++fit;
// if we reached end then don't reset sit since it would be UB
if (fit != data.end())
sit = fit->begin();
}
return *this;
}
T& operator*() const { return *sit; }
};
public:
std::vector<std::vector<T>> data;
iterator begin() { return iterator(this->data, true); }
iterator end() { return iterator(this->data, false); }
};
A small test:
int main() {
vector2d<int> data;
data.data.push_back(vector<int>());
data.data.push_back(vector<int>());
data.data.push_back(vector<int>());
for (int i = 1; i < 5; ++i)
{
data.data[0].push_back(i);
data.data[1].push_back(i*2);
data.data[2].push_back(i*3);
}
for (auto i : data)
{
cout << i << endl;
}
return 0;
}
The behavior is rather simple but you must make sure that it's always consistent for all the edge cases.
A pretty minimal range type:
template<class It>
struct range_t {
private:
It b, e;
public:
It begin() const { return b; }
It end() const { return e; }
decltype(auto) front() const { return *b; }
decltype(auto) back() const { return *std::prev(e); }
bool empty() const { return b==e; }
range_t without_front( std::size_t n = 1 ) const {
auto r = *this;
std::advance(r.b,n);
return r;
}
range_t without_back( std::size_t n = 1 ) const {
auto r = *this;
std::advance(r.e,std::ptrdiff_t(-n));
return r;
}
range_t(It s, It f):b(std::move(s)), e(std::move(f)) {}
range_t():b(), e() {}
};
template<class It>
range_t<It> range( It b, It e ) {
return {std::move(b), std::move(e)};
}
Doing this task is far easier with ranges than with iterators.
template<class Outer, class Inner>
struct stacked_range_t {
range_t<Outer> outer;
stacked_range_t()=default;
stacked_range_t( range_t<Outer> o ):outer(std::move(o)) {}
struct iterator {
private:
range_t<Outer> outer;
range_t<Inner> inner;
public:
iterator(
range_t<Outer> o,
range_t<Inner> i
):outer(std::move(o)), inner(std::move(i)) {}
iterator()=default;
friend auto mytie(iterator const& it) {
return std::tie( it.outer.begin(), it.inner.begin(), it.inner.end() );
}
friend bool operator==(iterator const& lhs, iterator const& rhs) {
return mytie(lhs)==mytie(rhs);
}
friend bool operator!=(iterator const& lhs, iterator const& rhs) {
return mytie(lhs)==mytie(rhs);
}
using difference_type = std::ptrdiff_t;
using value_type = typename std::iterator_traits<Inner>::value_type;
using pointer = typename std::iterator_traits<Inner>::pointer;
using reference = typename std::iterator_traits<Inner>::reference;
using iterator_category = std::input_iterator_tag;
reference operator*() const {
return *inner.begin();
}
pointer operator->() const {
return inner.begin().operator->();
}
iterator& operator++() {
using std::begin; using std::end;
inner = inner.without_front();
while (inner.empty())
{
outer = outer.without_front();
if (!outer.empty())
inner = range( begin(outer.front()), end(outer.front()) );
}
return *this;
}
iterator operator++(int) {
auto it = *this;
++*this;
return it;
}
};
iterator end() const {
return { range( outer.end(), outer.end() ), {} };
}
// a bit tricky:
iterator begin() const {
if (outer.empty()) return end();
auto rout = outer;
while( !rout.empty() ) {
using std::begin; using std::end;
auto rin = range( begin(rout.front()), end(rout.front()) );
if (!rin.empty())
return {std::move(rout), std::move(rin)};
rout = rout.without_front();
}
return end();
}
};
and a function to create it:
template<class Range>
auto make_stacked_range(Range& r) {
using std::begin; using std::end;
using Outer = decltype(begin(r));
using Inner = decltype(begin(*begin(r));
return stacked_range_t<Outer, Inner>{
{begin(r), end(r)}
};
}
there probably are typos. Use of C++1z features can be worked around with overly annoying decltype expressions and helper traits classes.
Relies on the iterators being trivially constructible, and such trivially constructed iterators are equal.
try to use template recursion,e.g.:
#include <stdio.h>
#include <vector>
template <typename V>
void f(V& v){
for(auto& e : v){
f(e);
}
printf("\n");
}
template <>
void f(int& v){
printf("%d ",v);
}
int main(){
std::vector<int> v1={1,2};
f(v1);
std::vector<std::vector<int> > v2={{3,4},{5,6,7}};
f(v2);
return 0;
};
With range/v3:
for (auto& x : myVector | ranges::view::join)
{
foo(x); // x is an object of type MyClass&
}
Demo
I want to create a custom map that that actually uses a fixed set of keys, but should behave like a std::map. Basically I use an array internally and map the keys to indexes, allowing very efficient lookup. I am however struggling to implement iterators that behave like std::map iterators, because I do not have internal std::pairs that I can hand out references to.
Is it possible to implement this as a zero-overhead abstraction while retaining the std::map interface, in particular the iterators?
The best i could come up with as operator* is to return a rvalue std::pair<key_type, mapped_type*>, which basically allows for the same operations, but unfortunately with different usage patterns.
I have also tried std::pair<key_type, boost::referene_wrapper<mapped_type>>, but that still doesn't allow for(auto& elem : map) and often requires elem.second.get() for reasons I do not understand.
I am happy to use boost or lightweight header libraries, if there is anything that helps for the use case.
To illustrate the case, here is a minimal example with a map that contains all letters 'a'-'z'.
using letter = char;
static const letter letter_begin = 'a';
static const letter letter_end = 'z' + 1;
template <typename T>
class letter_map
{
private:
using self = letter_map<T>;
template <typename IT, typename M>
class iterator_base : public std::iterator<std::input_iterator_tag, T>
{
public:
iterator_base(letter index, M& map) : index_(index), data_(map)
{
}
using self_iter = iterator_base<IT, M>;
IT operator*()
{
return IT(index_, &data_[index_]);
}
self_iter& operator++()
{
index_++;
return *this;
}
self_iter operator++(int)
{
self_iter tmp(*this);
operator++();
return tmp;
}
bool operator==(self_iter other) const
{
assert(&data_ == &other.data_);
return index_ == other.index_;
}
bool operator!=(self_iter other) const
{
return !(*this == other);
}
private:
letter index_;
M& data_;
};
public:
using key_type = letter;
using mapped_type = T;
using value_type = std::pair<const key_type, mapped_type*>;
using const_value_type = std::pair<const key_type, const mapped_type*>;
private:
static const size_t data_size = letter_end - letter_begin;
using container_type = std::array<mapped_type, data_size>;
public:
using iterator = iterator_base<value_type, self>;
using const_iterator = iterator_base<const_value_type, const self>;
public:
mapped_type& operator[](letter l)
{
return data_[l - letter_begin];
}
const mapped_type& operator[](letter l) const
{
return data_[l - letter_begin];
}
auto begin()
{
return iterator(letter_begin, *this);
}
auto end()
{
return iterator(letter_end, *this);
}
auto begin() const
{
return const_iterator(letter_begin, *this);
}
auto end() const
{
return const_iterator(letter_end, *this);
}
private:
container_type data_;
};
void print_string_letter_map(const letter_map<std::string>& lm)
{
for (auto elem : lm)
{
std::cout << elem.first << "->" << *(elem.second) << std::endl;
}
}
template<typename T>
class std_letter_map : public std::map<letter, T>
{
public:
std_letter_map()
{
for (letter l = letter_begin; l != letter_end; ++l) {
this->emplace(l, T());
}
}
};
void print_string_std_letter_map(const std_letter_map<std::string>& lm)
{
for (const auto& elem : lm)
{
std::cout << elem.first << "->" << elem.second << std::endl;
}
}
int main()
{
letter_map<std::string> lm;
// usually I would use auto& elem here
for (auto elem : lm) {
auto let = elem.first;
// usually this would be without the *
auto& str = *(elem.second);
str = std::string("foo ") + let;
}
print_string_letter_map(lm);
return 0;
}
Implementing operator * is easy - just return std::pair<const Key, Value&> or ..., Value const&> for const iterator, like in this simplified example:
template <typename T>
class iterator_array_as_map
{
public:
iterator_array_as_map(T* array, int index)
: array(array), index(index)
{}
bool operator == (const iterator_array_as_map& other) const
{
return index == other.index;
}
bool operator != (const iterator_array_as_map& other) const
{
return index != other.index;
}
iterator_array_as_map& operator ++ ()
{
++index;
return *this;
}
auto operator * ()
{
return std::pair<const int, T&>(index, array[index]);
}
private:
T* array;
int index;
};
And usage:
int main() {
int array[2] = {2, 4};
auto begin = iterator_array_as_map<int>(array, 0);
auto end = iterator_array_as_map<int>(array, 2);
for (auto it = begin; it != end; ++it)
{
std::cout << (*it).first << " " << (*it).second << std::endl;
}
(*begin).second = 7;
for (auto it = begin; it != end; ++it)
{
std::cout << (*it).first << " " << (*it).second << std::endl;
}
}
operator -> is a little harder - since you must return something which needs to emulate pointer to std::pair - but if do not mind about dynamic memory fragmentation - you can just return std::shared_ptr<std::pair<....>>...
auto operator -> ()
{
return std::make_shared<std::pair<const int, T&>>(index, array[index]);
}
If you do not want to use dynamic memory - then you might try with pointer to itself solution:
template<typename data>
class pointer_to_data
{
public:
template<typename ...Arg>
pointer_to_data(Arg&&... arg)
: data{std::forward<Arg>(arg)...}
{}
Data* operator -> ()
{
return &data;
}
private:
Data data;
};
Just return the above instead shared_ptr...
See this what-is-the-correct-way-of-using-c11s-range-based-for, section "The special case of proxy iterators". It is not possible to define for(auto&e:b) if b is e.g. std::vector<bool>- because in general it is not possible to have reference to temporary, and this very container is similar to yours in this sense that it has "Special" reference type.
You can try to have special member in your iterator keeping the "return value"- but that would be troublesome - so probably no better that this presented by me solution exist.
You could probably use zip_view from Eric Niebler's range library
https://github.com/ericniebler/range-v3
How do I create a custom class to loop over consecutive pairs of items in a STL container using a range-based loop?
This is the syntax and output I want:
std::list<int> number_list;
number_list.push_back(1);
number_list.push_back(2);
number_list.push_back(3);
auto paired_list = Paired(number_list);
for (const auto & pair : paired_list) {
std::printf("The pair is (%d, %d)\n", *(pair[0]), *(pair[1]));
// or
//std::printf("The pair is (%d, %d)\n", *(pair.first), *(pair.second));
}
// output:
// The pair is (1, 2)
// The pair is (2, 3)
I know these (and more) are needed, but I can't figure it out:
template <class T>
class Paired {
???
class iterator {
???
}
iterator begin() {
...
}
iterator end() {
...
}
}
Don't worry about const modifiers.
No boost.
Do not modify or copy objects in the container.
Here's what I would do.
#include <iterator>
#include <utility>
template <typename FwdIt> class adjacent_iterator {
public:
adjacent_iterator(FwdIt first, FwdIt last)
: m_first(first), m_next(first == last ? first : std::next(first)) { }
bool operator!=(const adjacent_iterator& other) const {
return m_next != other.m_next; // NOT m_first!
}
adjacent_iterator& operator++() {
++m_first;
++m_next;
return *this;
}
typedef typename std::iterator_traits<FwdIt>::reference Ref;
typedef std::pair<Ref, Ref> Pair;
Pair operator*() const {
return Pair(*m_first, *m_next); // NOT std::make_pair()!
}
private:
FwdIt m_first;
FwdIt m_next;
};
template <typename FwdIt> class adjacent_range {
public:
adjacent_range(FwdIt first, FwdIt last)
: m_first(first), m_last(last) { }
adjacent_iterator<FwdIt> begin() const {
return adjacent_iterator<FwdIt>(m_first, m_last);
}
adjacent_iterator<FwdIt> end() const {
return adjacent_iterator<FwdIt>(m_last, m_last);
}
private:
FwdIt m_first;
FwdIt m_last;
};
template <typename C> auto make_adjacent_range(C& c) -> adjacent_range<decltype(c.begin())> {
return adjacent_range<decltype(c.begin())>(c.begin(), c.end());
}
#include <iostream>
#include <vector>
using namespace std;
void test(const vector<int>& v) {
cout << "[ ";
for (const auto& p : make_adjacent_range(v)) {
cout << p.first << "/" << p.second << " ";
}
cout << "]" << endl;
}
int main() {
test({});
test({11});
test({22, 33});
test({44, 55, 66});
test({10, 20, 30, 40});
}
This prints:
[ ]
[ ]
[ 22/33 ]
[ 44/55 55/66 ]
[ 10/20 20/30 30/40 ]
Notes:
I haven't exhaustively tested this, but it respects forward iterators (because it doesn't try to use operations beyond ++, !=, and *).
range-for has extremely weak requirements; it doesn't require all of the things that forward iterators are supposed to provide. Therefore I have achieved range-for's requirements but no more.
The "NOT m_first" comment is related to how the end of the range is approached. An adjacent_iterator constructed from an empty range has m_first == m_next which is also == last. An adjacent_iterator constructed from a 1-element range has m_first pointing to the element and m_next == last. An adjacent_iterator constructed from a multi-element range has m_first and m_next pointing to consecutive valid elements. As it is incremented, eventually m_first will point to the final element and m_next will be last. What adjacent_range's end() returns is constructed from (m_last, m_last). For a totally empty range, this is physically identical to begin(). For 1+ element ranges, this is not physically identical to a begin() that has been incremented until we don't have a complete pair - such iterators have m_first pointing to the final element. But if we compare iterators based on their m_next, then we get correct semantics.
The "NOT std::make_pair()" comment is because make_pair() decays, while we actually want a pair of references. (I could have used decltype, but iterator_traits will tell us the answer too.)
The major remaining subtleties would revolve around banning rvalues as inputs to make_adjacent_range (such temporaries would not have their lives prolonged; the Committee is studying this issue), and playing an ADL dance to respect non-member begin/end, as well as built-in arrays. These exercises are left to the reader.
edit I was using transform.
Use adjacent_difference.
The second version takes a binary function which transforms the two values into a new
(different) value:
string make_message(int first, int second) {
ostringstream oss;
oss << "The pair is (" << first << ", " << second << ")";
return oss.str();
}
We can now transform the adjacent pairs into a third range. We'll use the ostream_iterator to use cout like a range:
list<int> numbers;
//...
adjacent_difference(numbers.begin(), numbers.end(),
ostream_iterator<string>(cout, "\n"),
make_message);
2nd edit
I found a question on comp.lang.c++.moderated asking why there aren't more 'adjacent' functions in the standard library such as for_each_adjacent. The reply said they were trivial to implement using std::mismatch.
I think this would be a better direction to go than implementing a special adjacent iterator.
Try this.
#include <list>
#include <iostream>
template<class T, class TIter = typename T::iterator, class TVal = typename T::value_type>
class PairedImpl {
T& m_t;
public:
class Iter {
TIter m_it;
public:
Iter(const TIter & it) : m_it(it) {}
bool operator!=(const Iter& it) { return m_it != it.m_it; }
Iter& operator++() { ++m_it; return *this; }
const Iter & operator *() const { return *this; }
const TVal & first() const { return *m_it; }
const TVal & second() const { return *std::next(m_it); }
};
PairedImpl(T& t) : m_t(t) {}
Iter begin() { return Iter(m_t.begin()); }
Iter end() {
TIter end = m_t.end();
return Iter(m_t.empty() ? end : --end);
}
};
template<class T>
PairedImpl<T> Paired(T& t) {
return PairedImpl<T>(t);
}
Usage
int main()
{
std::list<int> lst;
lst.push_back(1);
lst.push_back(2);
lst.push_back(3);
lst.push_back(4);
lst.push_back(5);
for (const auto & pair : Paired(lst)) {
std::cout << "(" << pair.first() << ", " << pair.second() << ")" << std::endl;
}
return 0;
}
Okay, an hour with no answers, I've come up with a solution which works. Note that this uses my own FixedLengthVector which is exactly what it sounds like.
template <class T>
class Grouped {
private:
// length of grouped objects
static const unsigned length_ = 2;
// hold pointer to base container to avoid comparing incompatible iterators
T * base_container_;
public:
// constructor
Grouped(T & base_container) :
base_container_(&base_container) {
}
// iterator
class iterator {
private:
// hold pointer to base container to avoid comparing incompatible iterators
T * base_container_;
// hold pointers to objects in base container
FixedLengthVector<length_, typename T::value_type *> ptr_;
// hold iterator to last object
typename T::iterator last_iterator_;
public:
// constructor
iterator(T & base_container, typename T::iterator & it)
: base_container_(&base_container),
last_iterator_(it) {
// set up pointers if possible
unsigned i = 0;
// check for end iterator
if (last_iterator_ == base_container_->end()) {
ptr_.fill(NULL);
return;
}
// set up first object
ptr_[0] = &*last_iterator_;
// set up next objects
for (unsigned i = 1; i < length_; ++i) {
++last_iterator_;
if (last_iterator_ == base_container_->end()) {
ptr_.fill(NULL);
return;
}
ptr_[i] = &*last_iterator_;
}
}
// dereference operator
FixedLengthVector<length_, typename T::value_type *> & operator * (void) {
assert(ptr_[0] != NULL);
return ptr_;
}
// pre-increment
iterator & operator ++ (void) {
// can't increase past end
assert(last_iterator_ != base_container_->end());
// find next iterator
++last_iterator_;
if (last_iterator_ == base_container_->end()) {
ptr_.fill(NULL);
return * this;
}
// cycle pointers left
for (unsigned i = 1; i < length_; ++i) {
ptr_[i - 1] = ptr_[i];
}
ptr_[length_ - 1] = &*last_iterator_;
return * this;
}
// equality comparison
bool operator == (const iterator & that) const {
return base_container_ == that.base_container_ &&
last_iterator_ == that.last_iterator_;
}
// inequality comparison
bool operator != (const iterator & that) const {
return !(*this == that);
}
};
// end iterator
iterator end() {
return iterator(*base_container_, base_container_->end());
}
// begin iterator
iterator begin() {
return iterator(*base_container_, base_container_->begin());
}
};
I found myself writing this just a bit ago:
template <long int T_begin, long int T_end>
class range_class {
public:
class iterator {
friend class range_class;
public:
long int operator *() const { return i_; }
const iterator &operator ++() { ++i_; return *this; }
iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }
bool operator ==(const iterator &other) const { return i_ == other.i_; }
bool operator !=(const iterator &other) const { return i_ != other.i_; }
protected:
iterator(long int start) : i_ (start) { }
private:
unsigned long i_;
};
iterator begin() const { return iterator(T_begin); }
iterator end() const { return iterator(T_end); }
};
template <long int T_begin, long int T_end>
const range_class<T_begin, T_end>
range()
{
return range_class<T_begin, T_end>();
}
And this allows me to write things like this:
for (auto i: range<0, 10>()) {
// stuff with i
}
Now, I know what I wrote is maybe not the best code. And maybe there's a way to make it more flexible and useful. But it seems to me like something like this should've been made part of the standard.
So is it? Was some sort of new library added for iterators over a range of integers, or maybe a generic range of computed scalar values?
The C++ standard library does not have one, but Boost.Range has boost::counting_range, which certainly qualifies. You could also use boost::irange, which is a bit more focused in scope.
C++20's range library will allow you to do this via view::iota(start, end).
As far as I know, there is no such class in C++11.
Anyway, I tried to improve your implementation. I made it non-template, as I don't see any advantage in making it template. On the contrary, it has one major disadvantage : that you cannot create the range at runtime, as you need to know the template arguments at compile time itself.
//your version
auto x = range<m,n>(); //m and n must be known at compile time
//my version
auto x = range(m,n); //m and n may be known at runtime as well!
Here is the code:
class range {
public:
class iterator {
friend class range;
public:
long int operator *() const { return i_; }
const iterator &operator ++() { ++i_; return *this; }
iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }
bool operator ==(const iterator &other) const { return i_ == other.i_; }
bool operator !=(const iterator &other) const { return i_ != other.i_; }
protected:
iterator(long int start) : i_ (start) { }
private:
unsigned long i_;
};
iterator begin() const { return begin_; }
iterator end() const { return end_; }
range(long int begin, long int end) : begin_(begin), end_(end) {}
private:
iterator begin_;
iterator end_;
};
Test code:
int main() {
int m, n;
std::istringstream in("10 20");
if ( in >> m >> n ) //using in, because std::cin cannot be used at coliru.
{
if ( m > n ) std::swap(m,n);
for (auto i : range(m,n))
{
std::cout << i << " ";
}
}
else
std::cout <<"invalid input";
}
Output:
10 11 12 13 14 15 16 17 18 19
Onine demo.
I wrote a library called range for exactly the same purpose except it is a run-time range, and the idea in my case came from Python. I considered a compile-time version, but in my humble opinion there is no real advantage to gain out the compile-time version. You can find the library on bitbucket, and it is under Boost License: Range. It is a one-header library, compatible with C++03 and works like charm with range-based for loops in C++11 :)
Features:
A true random access container with all the bells and whistles!
Ranges can be compared lexicographically.
Two functions exist(returns
bool), and find(returns iterator) to check the existence of a number.
The library is unit-tested using CATCH.
Examples of basic
usage, working with standard containers, working with standard
algorithms and working with range based for loops.
Here is a one-minute introduction. Finally, I welcome any suggestion about this tiny library.
I found that boost::irange was much slower than the canonical integer loop. So I settled on the following much simpler solution using a preprocessor macro:
#define RANGE(a, b) unsigned a=0; a<b; a++
Then you can loop like this:
for(RANGE(i, n)) {
// code here
}
This range automatically starts from zero. It could be easily extended to start from a given number.
Here is a simpler form which is working nicely for me. Are there any risks in my approach?
r_iterator is a type which behaves, as much as possible, like a long int. Therefore many operators such as == and ++, simply pass through to the long int. I 'expose' the underlying long int via the operator long int and operator long int & conversions.
#include <iostream>
using namespace std;
struct r_iterator {
long int value;
r_iterator(long int _v) : value(_v) {}
operator long int () const { return value; }
operator long int& () { return value; }
long int operator* () const { return value; }
};
template <long int _begin, long int _end>
struct range {
static r_iterator begin() {return _begin;}
static r_iterator end () {return _end;}
};
int main() {
for(auto i: range<0,10>()) { cout << i << endl; }
return 0;
}
(Edit: - we can make the methods of range static instead of const.)
This might be a little late but I just saw this question and I've been using this class for a while now :
#include <iostream>
#include <utility>
#include <stdexcept>
template<typename T, bool reverse = false> struct Range final {
struct Iterator final{
T value;
Iterator(const T & v) : value(v) {}
const Iterator & operator++() { reverse ? --value : ++value; return *this; }
bool operator!=(const Iterator & o) { return o.value != value; }
T operator*() const { return value; }
};
T begin_, end_;
Range(const T & b, const T & e) : begin_(b), end_(e) {
if(b > e) throw std::out_of_range("begin > end");
}
Iterator begin() const { return reverse ? end_ -1 : begin_; }
Iterator end() const { return reverse ? begin_ - 1: end_; }
Range() = delete;
Range(const Range &) = delete;
};
using UIntRange = Range<unsigned, false>;
using RUIntRange = Range<unsigned, true>;
Usage :
int main() {
std::cout << "Reverse : ";
for(auto i : RUIntRange(0, 10)) std::cout << i << ' ';
std::cout << std::endl << "Normal : ";
for(auto i : UIntRange(0u, 10u)) std::cout << i << ' ';
std::cout << std::endl;
}
have you tried using
template <class InputIterator, class Function>
Function for_each (InputIterator first, InputIterator last, Function f);
Most of the time fits the bill.
E.g.
template<class T> void printInt(T i) {cout<<i<<endl;}
void test()
{
int arr[] = {1,5,7};
vector v(arr,arr+3);
for_each(v.begin(),v.end(),printInt);
}
Note that printInt can OFC be replaced with a lambda in C++0x.
Also one more small variation of this usage could be (strictly for random_iterator)
for_each(v.begin()+5,v.begin()+10,printInt);
For Fwd only iterator
for_each(advance(v.begin(),5),advance(v.begin(),10),printInt);
You can easily generate an increasing sequence in C++11 using std::iota():
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
template<typename T>
std::vector<T> range(T start, T end)
{
std::vector<T> r(end+1-start, T(0));
std::iota(r.begin(), r.end(), T(start));//increasing sequence
return r;
}
int main(int argc, const char * argv[])
{
for(auto i:range<int>(-3,5))
std::cout<<i<<std::endl;
return 0;
}
I'm not sure if this is a good question or not — please close it if not.
I set out to write (using boost::coordinate_vector as a starting point) a sparse_vector template class that efficiently implements a vector-like interface, but is sparse. It implements all the usual vector operations and a fast sparse iterator that iterates over the set elements. It also has a fast version of rotate. I need this class because I have a write-once-read-many use case, and I use many of these sparse_vectors.
I have no experience writing template classes, so I know that there are things that I could be doing better. How can I improve this class?
// sparse_vector is a sparse version of std::vector. It stores index-value pairs, and a size, which
// represents the size of the sparse_vector.
#include <algorithm>
#include <ostream>
#include <iostream>
#include <vector>
#include <boost/iterator.hpp>
#include <boost/iterator/iterator_adaptor.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/iterator/reverse_iterator.hpp>
#include <boost/optional.hpp>
#include <glog/logging.h>
template<typename T>
class sparse_vector {
public:
typedef T value_type;
typedef T* pointer;
typedef T& reference;
typedef const T& const_reference;
typedef size_t size_type;
private:
struct ElementType {
ElementType(size_type i, T const& t): index(i), value(t) {}
ElementType(size_type i, T&& t): index(i), value(move(t)) {}
ElementType(size_type i): index(i) {} // allows lower_bound to work
ElementType(ElementType const&) = default;
size_type index;
value_type value;
};
typedef std::vector<ElementType> array_type;
public:
typedef typename array_type::difference_type difference_type;
private:
typedef const T* const_pointer;
typedef sparse_vector<T> self_type;
typedef typename array_type::const_iterator const_subiterator_type;
typedef typename array_type::iterator subiterator_type;
struct IndexCompare {
bool operator()(ElementType const& a, ElementType const& b) { return a.index < b.index; }
};
public:
// Construction and destruction
inline sparse_vector(): size_(0), sorted_filled_(0), data_() {
storage_invariants(); }
explicit inline sparse_vector(size_type size): size_(size), sorted_filled_(0), data_() {
storage_invariants(); }
inline sparse_vector(const sparse_vector<T> &v):
size_(v.size_), sorted_filled_(v.sorted_filled_), data_(v.data_) {
storage_invariants(); }
inline sparse_vector(sparse_vector<T>&& v):
size_(v.size_), sorted_filled_(v.sorted_filled_), data_(move(v.data_)) {
storage_invariants(); }
sparse_vector(const optional<T> &o): size_(1), sorted_filled_(0) {
if (o) {
append_element(0, *o);
sorted_filled_ = 1;
}
storage_invariants();
}
sparse_vector(const std::vector<T> &v):
size_(v.size()), sorted_filled_(0) {
data_.reserve(v.size());
for (auto it = v.begin(); it != v.end(); ++it)
append_element(it - v.begin(), *it);
sorted_filled_ = v.size();
storage_invariants();
}
sparse_vector(const sparse_vector<optional<T>> &sv):
size_(sv.size()), sorted_filled_(0) {
for (auto it = sv.sparse_begin(); it != sv.sparse_end(); ++it)
if (*it)
append_element(it.index(), **it);
sorted_filled_ = data_.size();
storage_invariants();
}
sparse_vector(size_type size, vector<pair<size_type, T>> init_list):
size_(size), sorted_filled_(0) {
for (auto it = init_list.begin(); it != init_list.end(); ++it)
append_element(it->first, it->second);
// require that the list be sorted?
// sorted_filled_ = data_.size();
storage_invariants();
}
/*template<class AE>
inline sparse_vector(const sparse_vector<AE> &ae):
size_(ae().size()), sorted_filled_(ae.nnz()), data_() {
storage_invariants();
for (auto it = ae.sparse_begin(); it != ae.sparse_end(); ++it) {
(*this)[it.index()] = static_cast<T>(*it);
}
}*/
// Conversion operators
// Copy sparse_vector to a vector filling in uninitialized elements with T()
operator std::vector<T>() {
std::vector<T> retval(size_);
for (auto it = data_.begin(); it != data_.end(); ++it)
retval[it.index()] = *it;
return retval;
}
// Copy sparse_vector to a vector filling in uninitialized elements with a default value.
void CopyToVector(std::vector<T>* v, T const& default_value = T()) {
size_type last_index = 0;
for (auto it = sparse_begin(); it != sparse_end(); ++it) {
for (size_type i = last_index; i < it.index(); ++i) {
(*v)[i] = default_value;
}
(*v)[it.index()] = *it;
last_index = it.index() + 1;
}
}
// Accessors
inline size_type size() const {
return size_;
}
inline size_type nnz_capacity() const {
return data_.capacity();
}
inline size_type nnz() const {
return data_.size();
}
// Resizing
inline void resize(size_type size, bool preserve = true) {
if (preserve) {
sort(); // remove duplicate elements.
subiterator_type it(lower_bound(data_.begin(), data_.end(), size, IndexCompare()));
data_.erase(it, data_.end());
} else {
data_.clear();
}
size_ = size;
sorted_filled_ = nnz();
storage_invariants();
}
// Reserving
inline void reserve(size_type non_zeros, bool preserve = true) {
if (preserve)
sort(); // remove duplicate elements.
else
data_.clear();
data_.reserve(non_zeros);
sorted_filled_ = nnz();
storage_invariants();
}
// Element support
// find_element returns a pointer to element i or NULL -- it never creates an element
inline pointer find_element(size_type i) {
return const_cast<pointer> (const_cast<const self_type&>(*this).find_element(i)); }
inline const_pointer find_element(size_type i) const {
sort();
const_subiterator_type it(lower_bound(data_.begin(), data_.end(), i, IndexCompare()));
if (it == data_.end() || it->index != i)
return NULL;
return &it->value;
}
// find_element_optional returns a boost::optional<T>
inline boost::optional<value_type> find_element_optional(size_type i) const {
CHECK_LT(i, size_);
sort();
const_subiterator_type it(lower_bound(data_.begin(), data_.end(), i, IndexCompare()));
if (it == data_.end() || it->index != i)
return boost::optional<value_type>();
return boost::optional<value_type>(it->value);
}
// Element access
// operator[] returns a reference to element i -- the const version returns a reference to
// a zero_ element if it doesn't exist; the non-const version creates it in that case.
inline const_reference operator[] (size_type i) const {
CHECK_LT(i, size_);
sort();
const_subiterator_type it(lower_bound(data_.begin(), data_.end(), i, IndexCompare()));
if (it == data_.end() || it->index != i)
return zero_;
return it->value;
}
inline reference operator[] (size_type i) {
CHECK_LT(i, size_);
sort();
subiterator_type it(lower_bound(data_.begin(), data_.end(), i, IndexCompare()));
if (it == data_.end() || it->index != i)
return insert_element(i, value_type/*zero*/());
else
return it->value;
}
// Element assignment
inline void append_element(size_type i, const_reference t) {
data_.emplace_back(i, t);
storage_invariants();
}
inline void append_element(size_type i, T&& t) {
data_.emplace_back(i, move(t));
storage_invariants();
}
inline void sorted_append_element(size_type i, const_reference t) {
// must maintain sort order
CHECK(sorted());
if (data_.size() > 0)
CHECK_LT(data_.back().index, i);
data_.emplace_back(i, t);
sorted_filled_ = data_.size();
storage_invariants();
}
/* This function is probably not useful.
* inline void sparse_pop_back() {
// ISSUE invariants could be simpilfied if sorted required as precondition
CHECK_GT(data_.size(), 0);
data_.pop_back();
sorted_filled_ = (std::min)(sorted_filled_, data_.size());
storage_invariants();
}*/
inline reference insert_element(size_type i, const_reference t) {
DCHECK(find_element(i) == NULL); // duplicate element
append_element(i, t);
return data_.back().value;
}
// Element clearing
// TODO(neil): consider replacing size_type functions with iterator functions
// replaces elements in the range [i, i] with "zero" (by erasing them from the map)
inline void clear_element(size_type i) {
sort();
subiterator_type it(lower_bound(data_.begin(), data_.end(), i, IndexCompare()));
if (it == data_.end() || it->index != i)
return;
data_.erase(it);
--sorted_filled_;
storage_invariants();
}
// replaces elements in the range [first, last) with "zero" (by erasing them from the map)
inline void clear_elements(size_type first, size_type last) {
CHECK_LE(first, last);
sort();
subiterator_type it_first(lower_bound(data_.begin(), data_.end(), first, IndexCompare()));
subiterator_type it_last(lower_bound(data_.begin(), data_.end(), last, IndexCompare()));
sorted_filled_ -= it_last - it_first;
data_.erase(it_first, it_last);
storage_invariants();
}
// Zeroing
inline void clear() { data_.clear(); sorted_filled_ = 0; storage_invariants(); }
// Comparison
inline bool operator==(const sparse_vector &v) const {
if (this == &v)
return true;
if (size_ != v.size_)
return false;
auto it_this = sparse_begin();
for (auto it = v.sparse_begin(); it != v.sparse_end(); ++it) {
if (it_this == sparse_end()
|| it_this.index() != it.index()
|| *it_this != *it)
return false;
++it_this;
}
return true;
}
// Assignment
inline sparse_vector& operator=(const sparse_vector &v) {
if (this != &v) {
size_ = v.size_;
sorted_filled_ = v.sorted_filled_;
data_ = v.data_;
}
storage_invariants();
return *this;
}
inline sparse_vector &assign_temporary(sparse_vector &v) {
swap(v);
return *this;
}
template<class AE>
inline sparse_vector& operator=(const sparse_vector<AE> &ae) {
self_type temporary(ae);
return assign_temporary(temporary);
}
// Swapping
inline void swap(sparse_vector &v) {
if (this != &v) {
std::swap(size_, v.size_);
std::swap(sorted_filled_, v.sorted_filled_);
data_.swap(v.data_);
}
storage_invariants();
}
inline friend void swap(sparse_vector &v1, sparse_vector &v2) { v1.swap(v2); }
// Sorting and summation of duplicates
inline bool sorted() const { return sorted_filled_ == data_.size(); }
inline void sort() const {
if (!sorted() && data_.size() > 0) {
// sort new elements and merge
std::sort(data_.begin() + sorted_filled_, data_.end(), IndexCompare());
std::inplace_merge(data_.begin(), data_.begin() + sorted_filled_, data_.end(),
IndexCompare());
// check for duplicates
size_type filled = 0;
for(size_type i = 1; i < data_.size(); ++i) {
if (data_[filled].index != data_[i].index) {
++filled;
if (filled != i) {
data_[filled] = data_[i];
}
} else {
CHECK(false); // duplicates
// value_data_[filled] += value_data_[i];
}
}
++filled;
sorted_filled_ = filled;
data_.erase(data_.begin() + filled, data_.end());
storage_invariants();
}
}
// Back element insertion and erasure
inline void push_back(const_reference t) {
CHECK(sorted());
data_.emplace_back(size(), t);
if (sorted_filled_ == data_.size())
++sorted_filled_;
++size_;
storage_invariants();
}
inline void pop_back() {
CHECK_GT(size_, 0);
clear_element(size_ - 1);
if (sorted_filled_ == size_)
--sorted_filled_;
--size_;
storage_invariants();
}
// Iterator types
private:
template<typename base_type, typename iterator_value_type>
class sparse_iterator_private
: public boost::iterator_adaptor<
sparse_iterator_private<base_type, iterator_value_type>, // Derived
base_type, // Base
iterator_value_type, // Value
boost::random_access_traversal_tag // CategoryOrTraversal
> {
private:
struct enabler {}; // a private type avoids misuse
public:
sparse_iterator_private()
: sparse_iterator_private<base_type, iterator_value_type>::iterator_adaptor_() {}
explicit sparse_iterator_private(base_type&& p)
: sparse_iterator_private<base_type, iterator_value_type>::iterator_adaptor_(p) {}
size_type index() const { return this->base()->index; }
iterator_value_type& value() const { return this->base()->value; }
private:
friend class boost::iterator_core_access;
iterator_value_type& dereference() const { return this->base()->value; }
};
template<typename container_type, typename iterator_value_type>
class iterator_private
: public boost::iterator_facade<
iterator_private<container_type, iterator_value_type>, // Derived
iterator_value_type, // Value
boost::random_access_traversal_tag, // CategoryOrTraversal
iterator_value_type&, // Reference
difference_type // Difference
> {
private:
struct enabler {}; // a private type avoids misuse
public:
iterator_private(): container_(NULL), index_(0) {}
iterator_private(container_type* container, size_type index)
: container_(container), index_(index) {}
iterator_private(iterator_private const& other)
: container_(other.container_), index_(other.index_) {}
iterator_private& operator=(iterator_private const& other) {
container_ = other.container_;
index_ = other.index_;
}
container_type& container() const { return *container_; }
size_type index() const { return index_; }
iterator_value_type& value() const { return dereference(); }
/* TODO(neil): how do you make this work?
template<typename U>
sparse_iterator_private<U> subsequent_sparse_iterator() {
return sparse_iterator_private<T>(lower_bound(container_.data_.begin(),
container_.data_.end(),
index_, IndexCompare()));
}
*/
private:
friend class boost::iterator_core_access;
iterator_value_type& dereference() const { return (*container_)[index_]; }
bool equal(iterator_private<container_type, iterator_value_type> const& other) const {
return index_ == other.index_ && container_ == other.container_; }
void increment() { ++index_; }
void decrement() { --index_; }
void advance(int n) { index_ += n; }
difference_type distance_to(iterator_private<container_type,
iterator_value_type> const& other) {
return index_ - other.index_; }
container_type* container_;
size_type index_;
};
public:
typedef sparse_iterator_private<typename array_type::iterator, value_type> sparse_iterator;
typedef sparse_iterator_private<
typename array_type::const_iterator, const value_type> const_sparse_iterator;
typedef iterator_private<sparse_vector, value_type> iterator;
typedef iterator_private<const sparse_vector, const value_type> const_iterator;
typedef boost::reverse_iterator<sparse_iterator> reverse_sparse_iterator;
typedef boost::reverse_iterator<const_sparse_iterator> const_reverse_sparse_iterator;
typedef boost::reverse_iterator<iterator> reverse_iterator;
typedef boost::reverse_iterator<const_iterator> const_reverse_iterator;
// Element lookup
// inline This function seems to be big. So we do not let the compiler inline it.
sparse_iterator sparse_find(size_type i) {
sort();
return sparse_iterator(lower_bound(data_.begin(), data_.end(), i, IndexCompare()));
}
// inline This function seems to be big. So we do not let the compiler inline it.
const_sparse_iterator sparse_find(size_type i) const {
sort();
return const_sparse_iterator(lower_bound(data_.begin(), data_.end(), i, IndexCompare()));
}
// the nth non-zero element
const_sparse_iterator nth_nonzero(size_type i) const {
CHECK_LE(data_.size(), i);
sort();
return const_sparse_iterator(data_.begin() + i);
}
// Forward iterators
inline sparse_iterator sparse_begin() { return sparse_find(0); }
inline sparse_iterator sparse_end() { return sparse_find(size_); }
inline const_sparse_iterator sparse_begin() const { return sparse_find(0); }
inline const_sparse_iterator sparse_end() const { return sparse_find(size_); }
inline iterator begin() { return iterator(this, 0); }
inline iterator end() { return iterator(this, size()); }
inline const_iterator begin() const { return const_iterator(this, 0); }
inline const_iterator end() const { return const_iterator(this, size()); }
// Reverse iterators
inline reverse_sparse_iterator sparse_rbegin() {
return reverse_sparse_iterator(sparse_end()); }
inline reverse_sparse_iterator sparse_rend() {
return reverse_sparse_iterator(sparse_begin()); }
inline const_reverse_sparse_iterator sparse_rbegin() const {
return const_reverse_sparse_iterator(sparse_end()); }
inline const_reverse_sparse_iterator sparse_rend() const {
return const_reverse_sparse_iterator(sparse_begin()); }
inline reverse_iterator rbegin() {
return reverse_iterator(end()); }
inline reverse_iterator rend() {
return reverse_iterator(begin()); }
inline const_reverse_iterator rbegin() const {
return const_reverse_iterator(end()); }
inline const_reverse_iterator rend() const {
return const_reverse_iterator(begin()); }
// Mutating algorithms
// like vector::erase (erases the elements from the container and shrinks the container)
inline void erase(iterator const& first, iterator const& last) {
clear_elements(first.index(), last.index());
CHECK(sorted());
subiterator_type it_first(lower_bound(data_.begin(), data_.end(),
first.index(), IndexCompare()));
size_t n = last.index() - first.index();
for (auto it = it_first; it != data_.end(); ++it)
it->index -= n;
size_ -= n;
}
// like vector::insert (insert elements into the container and causes the container to grow)
inline void insert(iterator const& index, size_type n) {
sort();
subiterator_type it_first(lower_bound(data_.begin(), data_.end(),
index.index(), IndexCompare()));
for (auto it = it_first; it != data_.end(); ++it)
it->index += n;
size_ += n;
}
// Removes all "zero" elements
void remove_zeros() {
size_type filled = 0;
for(size_type i = 0; i < data_.size(); ++i) {
if (i == sorted_filled_) {
// Once we've processed sorted_filled_ items, we know that whatever we've filled so
// far is sorted.
CHECK_LE(filled, sorted_filled_);
// Since sorted_filled_ only shrinks here, and i only grows, we won't enter this
// condition twice.
sorted_filled_ = filled;
}
if (data_[i].value != value_type/*zero*/()) {
if (filled != i) {
data_[filled] = data_[i];
}
++filled;
}
}
data_.erase(data_.begin() + filled, data_.end());
storage_invariants();
}
void rotate(size_type first, size_type middle, size_type last) {
CHECK_LT(first, middle);
CHECK_LT(middle, last);
sort();
subiterator_type it_first(lower_bound(data_.begin(), data_.end(),
first, IndexCompare()));
subiterator_type it_middle(lower_bound(data_.begin(), data_.end(),
middle, IndexCompare()));
subiterator_type it_last(lower_bound(data_.begin(), data_.end(),
last, IndexCompare()));
for (auto it = it_first; it != it_middle; ++it)
it->index += last - middle;
for (auto it = it_middle; it != it_last; ++it)
it->index -= middle - first;
std::rotate(it_first, it_middle, it_last);
// array remains sorted
}
sparse_vector<value_type> concatenate(sparse_vector<value_type> const& other) const {
sparse_vector<value_type> retval(size() + other.size());
for (auto it = sparse_begin(); it != sparse_end(); ++it) {
retval[it.index()] = *it;
}
for (auto it = other.sparse_begin(); it != other.sparse_end(); ++it) {
retval[it.index() + size()] = *it;
}
return retval;
}
private:
void storage_invariants() const {
CHECK_LE(sorted_filled_, data_.size());
if (sorted())
CHECK_EQ(sorted_filled_, data_.size());
else
CHECK_LT(sorted_filled_, data_.size());
if (!data_.empty())
CHECK_LT(data_.back().index, size_);
for (size_type i = 1; i < sorted_filled_; ++i)
CHECK_GT(data_[i].index, data_[i - 1].index);
}
size_type size_;
mutable typename array_type::size_type sorted_filled_;
mutable array_type data_;
static const value_type zero_;
};
template<typename T>
const typename sparse_vector<T>::value_type sparse_vector<T>::zero_ = T();
template<typename T>
ostream& operator<<(ostream& os, const sparse_vector<T>& v) {
os << "[" << v.size() << ": ";
for (auto it = v.sparse_begin(); it != v.sparse_end(); ++it) {
os << "(" << it.index() << ": " << it.value() << ") ";
}
return os << "]";
}
For someone with little experience writing class templates, you've created something fairly sophisticated. Unfortunately I can't do a in-depth analysis at the moment: it would take too much time giving the amount of code presented and the generality of the question. I can only briefly respond to an overview.
For a start, sorting mutable data on operator[] const and begin/end methods is kind of scary. This class won't be thread-safe even for concurrent read access. Dealing with multiple threads is probably beyond the scope of this class, but this behavior is very peculiar so it should probably be well-documented if the class is intended for very general-purpose use.
Another alternative is to require the client sort the vector explicitly and use linear access methods to fetch data until this is done. This isn't quite as automagic as your solution, but it will make your class safer to use across threads for read-purposes.
It also seems apparent to me that you did not inline this code with the aid of a profiler. Don't do this without the profiler's help. I'm not just spouting generalities here; I work with raytracers and profile code with vtune and parallel studio on a daily basis as part of my job and inlining code like this has a detrimental effect on performance. The most obvious case is due to code bloat, but there's a second, less obvious case that most people seem to overlook.
Even for things like single line accessors, if you inline them all, you'll interfere with the optimizer's ability to make the best use of registers and other optimizations. Optimizing compilers work best on a function-by-function basis with small, well-written functions. They generally don't do as good a job at an inter-function level and if you start inlining everything, the result is akin to one big flat function accessing all kinds of data which even ICC (Intel's optimizing compiler) doesn't do such a great job at dealing with. For a start, the compiler can't read your mind, so inlining everything makes less-executed branches optimized equally as well as more commonly executed ones (basically compromising the compiler's ability to optimize the common branches).
With the profiler, you can see these more commonly executed branches and inline them selectively, but never start by inlining: profilers do a good job at telling you what to consider inlining, but never what shouldn't be inlined. In fact, you'll wreak havoc on your ability to profile your code by inlining almost everything when you start.
As for public interface, I can see that the whole point of this is to implement contiguity and reduce memory and access times (using std::vector for the backend), but given its nature, I think you would do better to provide an interface similar to map<int, T>. The push_back and pop_back behavior, for instance, is rather confusing. Consider an alternative with insert and erase methods only and a non-const operator[] which can create new indices (keys), or do this at least as a first step. You could provide an insert method which only takes const_reference which chooses an available index to be assigned and returns it (it could just be size() - 1 if you don't care about gaps).
From an implementation standpoint, the code seems straightforward enough. I don't have much to suggest there other than 'de-inlining' your code.
Since it appears as though speed is one of the motivating factors for creating this, here's a small tip. Instead of:
inline void sort() const {
if (!sorted() && data_.size() > 0) {
...
}
Consider taking that initial 'if' out and don't inline this function. Put it outside of the class definition. You use sort for all kinds of read-only access cases but it's an exceptional case: most read access should be operating on already sorted data. When you have exceptional cases like this, your optimizing compiler will generally do a much better job if the function isn't inlined and you put your check outside:
inline const_reference operator[] (size_type i) const {
CHECK_LT(i, size_);
if (need_sort() )
sort();
...
}
Having done micro-optimizations on a daily basis and with the aid of a profiler, I can assure you that this will generally be faster on most optimizing compilers including latest versions of GCC, ICC, and MSVC. Roughly speaking, the reason is because your operator[] function will be doing less and accessing less memory as a result (no inlined sort to deal with which should only be executed in exceptional circumstances).
Most importantly, I recommend that you grab a profiler and do some tests and see where your bottlenecks are with this.