I am trying to write an iterator adaptor that should call a member function (or access a member of the object) each time it is dereferenced. Here is an example of such an API:
vector<pair<int,int>> ps = {{1,"a"}, {2,"b"}, {3,"c"}};
// Pairs that represent ranges
auto rf = make_adaptor(ps.begin(), ps.end(), [](const auto& x) {return x.first;}
auto rs = make_adaptor(ps.begin(), ps.end(), [](auto& x) {return x.second;}
Should print out 123:
for_each(rf.first, rf.second, [](const auto& x){std::cout << x;});
Should set every second element of the pairs in ps:
for_each(rs.first, rs.second, [](auto& x){ x = "hello";});
I have tried writing an own iterator type together with a make_adaptor method, but I can't seem to get it to work:
template <typename Iterator, typename UnaryOp>
struct adaptor {
using value_type = std::result_of<UnaryOp(typename Iterator::reference)>::type;
using reference = value_type&;
using pointer = value_type*;
using difference_type = typename Iterator::difference_type;
using iterator_category = typename Iterator::iterator_category;
adaptor(){};
adaptor(Iterator it, UnaryOp func) : _it(it), _func(func) {}
reference operator*() const { return _func(*_it); }
pointer operator->() const { return &_func(*_it); }
bool operator==(const adaptor& other) const { return _it == other._it; }
bool operator!=(const adaptor& other) const { return _it != other._it; }
adaptor& operator++() {
++_it;
return *this;
}
Iterator _it;
UnaryOp _func;
};
template <typename Iterator, typename UnaryOp>
auto make_adaptor(Iterator first, Iterator last, UnaryOp func) {
return std::make_pair(adaptor<Iterator, UnaryOp>(first, func),
adaptor<Iterator, UnaryOp>(last, func));
};
The reason for this is the following: Let's say I have an algorithm convex_hull which works on points. But now I have objects which contains points as a member (struct A { points pos;};). I want to call convex_hull on a collection of As.
With range-v3, you may do something similar to:
const std::vector<std::pair<int,const char*>> ps = {{1,"a"}, {2,"b"}, {3,"c"}};
for (const auto s : ps | ranges::view::keys)
{
std::cout << " " << s;
}
for (const auto s : ps | ranges::view::transform([](const auto& p) { return p.first;} ))
{
std::cout << " " << s;
}
Demo
Related
For a C++17 restricted project I would like to have a standalone implementation of C++20 std::views::join(). I am aware of the existence of range-v3 but unfortunately the person in charge is unwilling to include further 3rd party libraries.
My aim is the write the C++17 equivalent of this (Implementation) --> solved see below
std::vector<std::vector<int>> data{{1,2},{1,2,3},{1,2,3,4}};
for(const auto & element : std::views::join(data)){
std::cout << element << "\n";
}
and the more difficult part this (Godbolt)
std::vector<std::vector<std::vector<int>>> data{{{1,2},{3,4}},{{5,6,7}}};
auto nested_join = std::views::join(std::views::join(data));
for(const auto element : nested_join){
std::cout << element << "\n";
}
I want to additionally emphasis the nesting of calls (std::views::join(std::views::join(data))), as they are the current road-block. To be more specific
How can I design my join_view class to be nestable [, while preserving the functionality which std::views::join provides (i.e. optionally modifiable elements Godbolt)?](refined/edited question)
I have successfully implemented a C++17 solution for the first part (Implementation), class code is provided at the end. The join_view wrapper class works by holding a reference to the nested object (which may or may not be const) and provides begin() and end() function to allow a range based for loop. These return an internal iterator (I think it fulfills the LegacyInputIterator requirements), for which the required operators (++,*,==,!=) are implemented.
Now lets consider my unsuccessful attempts to implement the second part.
Let's just try if I have written a super code that works as well for nesting the join_view construction. Nope. For a triple nested vector std::vector<std::vector<std::vector<int>>> and a twice applied join_view instead of a int as element type I receive a std::vector<int> (Implementation). If we take a look at the types of the nested construction auto deeper_view = join_view(join_view(data_deeper)); this expands in C++ Insights to join_view<std::vector<...>> deeper_view = join_view(join_view<std::vector<...>>(data_deeper)); which is obviously a sign of an issue?
I then tried changing all calls of the std::begin() and std::end() to their $.begin() counterpart, since these are the one defined for the join_view wrapper. Unfortunately this did also not help but now the C++ Insights output is unreadable and I cant follow it anymore.
Now I am no longer sure that this could even work therefore I am asking the question from above: How can I redesign my join_view class to be nestable?
I am aware of the following stackoverflow questions regarding std::views::join [join view how, join boost problem, join string_view problem, join compilation issue], but these do not consider implementation. I have as well tried understanding and reading the implementation of ranges-v3 join wrapper, which has provided to be difficult.
Current partially working status of standalone join_view wrapper:
template<typename T>
class join_view{
private:
T & ref_range;
using outer_iterator = decltype(ref_range.begin());
using inner_iterator = decltype((*ref_range.begin()).begin());
public:
join_view(T & range) : ref_range{range} {}
class iterator{
private:
outer_iterator outer;
inner_iterator inner;
public:
iterator(outer_iterator outer_, inner_iterator inner_): outer{outer_}, inner{inner_} {}
auto& operator*(){
return *inner;
}
auto& operator++(){
++inner;
if(inner != (*outer).end()){
return *this;
}
++outer;
inner = (*outer).begin();
return *this;
}
auto operator==(const iterator & other){
return outer == other.outer;
}
auto operator!=(const iterator & other){
return outer != other.outer;
}
};
auto begin(){
return iterator(ref_range.begin(), (*ref_range.begin()).begin());
}
auto end(){
return iterator(ref_range.end(),{});
}
};
You need to implement a recursive class template to achieve your goal. Here is a working quick and dirty prototype.
#include <cassert>
#include <iostream>
#include <type_traits>
#include <vector>
template <class T>
struct is_container : public std::false_type {};
// you'll need to declare specializations for the containers you need.
template <class T, class Alloc>
struct is_container<std::vector<T, Alloc>> : public std::true_type {};
// basic definition for our view
template <typename T, typename = void>
struct join_view;
// specialization for non-container types
template <typename T>
struct join_view<T, std::enable_if_t<!is_container<T>::value>> {
using contained_type = T;
using outer_const_iterator = const T*;
using const_iterator = const T*;
join_view(const T& t) : t_(t) {}
const_iterator begin() const { return &t_; }
const_iterator end() const { return begin() + 1; }
const T& t_;
};
// specialization for containers
template <typename Container>
struct join_view<Container, std::enable_if_t<is_container<Container>::value>> {
using contained_type = typename Container::value_type;
using outer_const_iterator = typename Container::const_iterator;
using inner_container_type = join_view<contained_type>;
using inner_const_iterator = typename inner_container_type::const_iterator;
friend inner_container_type;
class const_iterator {
friend join_view;
friend inner_container_type;
public:
const_iterator() = default;
const_iterator(const const_iterator&) = default;
const_iterator(const_iterator&&) = default;
const_iterator& operator=(const const_iterator&) = default;
const_iterator& operator=(const_iterator&&) = default;
private:
const_iterator(const Container* container, const outer_const_iterator& outer,
const inner_const_iterator& inner)
: container_(container), outer_(outer), inner_(inner) {}
const_iterator(const Container* container, outer_const_iterator outer)
: container_(container), outer_(outer) {
assert(outer_ == container_->end());
}
public:
const_iterator& operator++() {
if (++inner_ != inner_container_type{*outer_}.end()) return *this;
if (++outer_ != container_->end())
inner_ = inner_container_type{*outer_}.begin();
return *this;
}
bool operator==(const const_iterator& other) const {
if (outer_ == other.outer_) {
if (outer_ == container_->end()) return true;
return inner_ == other.inner_;
}
return false;
}
bool operator!=(const const_iterator& other) const {
return !(*this == other);
}
const auto& operator*() const
{
return *inner_;
}
private:
const Container* container_ = nullptr;
outer_const_iterator outer_;
inner_const_iterator inner_;
};
join_view(const Container& container) : outer_(container) {}
const_iterator begin() const {
return {&outer_, outer_.begin(),
inner_container_type{*(outer_.begin())}.begin()};
}
const_iterator end() const { return {&outer_, outer_.end()}; }
const Container& outer_;
};
template <typename T>
auto make_join_view(const T& t)
{
return join_view<T>(t);
}
int main() {
static_assert(is_container<std::vector<int>>::value);
static_assert(!is_container<int>::value);
int test_int = 42;
for (auto x : make_join_view(test_int)) std::cout << x << std::endl;
std::cout << std::endl;
std::vector<int> v{1, 2, 3};
for (const auto& x : make_join_view(v)) std::cout << x << std::endl;
std::cout << std::endl;
std::vector<std::vector<int>> vv{{1}, {2, 3}, {4, 5, 6}};
for (const auto& x : make_join_view(vv)) std::cout << x << std::endl;
std::cout << std::endl;
std::vector<std::vector<std::vector<int>>> vvv{ {{1}, {2, 3}, {4, 5, 6}}, {{11}, {22, 33}, {44, 55, 66}} };
for (const auto& x : make_join_view(vvv)) std::cout << x << std::endl;
}
It's very rough, as it only handles very basic traversals, but should work for most container types.
I think is_container<> can be rewritten to check for the presence of Container::const_iterator. That would allow join_view to work on any container, but also on any view.
Note: make sure your implementation uses the name const_iterator, that's absolutely imperative for this scheme to work.
You can play with the code here: https://godbolt.org/z/jhe93b1rx
I have an abstract base class T and another class holding a vector of unique pointers to T. The class should support two function returning references to the entries. One of them should provide read-access, the other one should be able to modify the values in the unique_ptrs but not the pointers:
class A {
private:
std::vector<std::unique_ptr<T>> data;
public:
auto access() -> RefRange<T>;
auto const_access() -> ConstRefRange<T>;
};
The ranges should satisfy the requirements of a std::random_access_range.
How can I do this without allocating additional memory in C++17 (boost is available)?
If you have boost, much of this is done by the range adaptor indirected
class A {
private:
static auto add_const(T & t) -> const T & { return t; }
std::vector<std::unique_ptr<T>> data;
using indirected = boost::adaptors::indirected;
using transformed = boost::adaptors::transformed;
public:
auto access() { return data | indirected; }
auto const_access() const { return data | indirected | transformed(add_const); }
};
Or with std::views::transform in C++20
class A {
private:
static auto indirect(const std::unique_ptr<T> & ptr) -> T & { return *ptr; }
static auto add_const(T & t) -> const T & { return t; }
std::vector<std::unique_ptr<T>> data;
using transform = std::views::transform;
public:
auto access() { return data | transform(indirect); }
auto const_access() const { return data | transform(indirect) | transform(add_const); }
};
If you have <experimental/propagate_const>, I'd use that instead of whichever transform(add_const).
class A {
private:
std::vector<std::experimental::propagate_const<std::unique_ptr<T>>> data;
using indirected = boost::adaptors::indirected;
public:
auto access() { return data | indirected; }
auto const_access() const { return data | indirected; }
};
You can implement a wrapper type which wraps the std::vector::iterator and provides a reference to the element contained by the std::unique_ptr on dereference.
Then return a simple struct containing begin and end iterators of these types.
Ex:
template<class T>
struct MyIterator
{
typename std::vector<std::unique_ptr<T>>::iterator iter; // or const_iterator for the const version
decltype(auto) operator*() const { return *iter->get(); }
// implement the rest of the iterator functionality
// ...
};
template<class T>
struct RefRange
{
MyIterator<T> first, last;
auto begin() const { return this->first; }
auto end() const { return this->last; }
};
class A {
private:
std::vector<std::unique_ptr<T>> data;
public:
auto access() -> RefRange<T> { return { data.begin(), data.end() }; }
};
Wrapper around iterator is the first thing that comes to my mind.
It's a quick and dirty example, but you should get the idea I suppose.
Demo: https://godbolt.org/z/z365js1T9
#include <memory>
#include <vector>
#include <iostream>
#include <initializer_list>
template<typename T>
struct S
{
using VecType = std::vector<std::unique_ptr<T>>;
VecType v{};
class RefRange
{
public:
RefRange(VecType& v) : r{v} {}
struct iterator
{
typename VecType::iterator underlying_iterator;
//below just return const T& for ConstRefRange
T& operator*() const { return *underlying_iterator->get();}
iterator& operator++(){++underlying_iterator; return *this;}
iterator operator++(int) {iterator ret{*this}; ++(*this); return ret;}
friend bool operator==(const iterator& l, const iterator& r)
{
return l.underlying_iterator==r.underlying_iterator;
}
friend bool operator!=(const iterator& l, const iterator& r)
{
return !(l==r);
}
};
iterator begin() {return iterator{r.begin()};}
iterator end() {return iterator{r.end()};}
private:
VecType& r;
};
RefRange refs() {return RefRange{v};}
};
int main()
{
S<int> s;
s.v.push_back(std::make_unique<int>(5));
s.v.push_back(std::make_unique<int>(6));
s.v.push_back(std::make_unique<int>(7));
auto r = s.refs();
for (auto&& el : r) {std::cout << el;}
}
I want to create a custom iterator wrapper, for example, enumerate: given a pair of iterators over type T, it would return an iterable over type std::pair<const int, T&>, where the first element of the pair will take values 0, 1, 2, so on.
I have a problem figuring out what should be value_type and reference of my iterator. I want to support two behaviours:
First, referencing values of the underlying sequence:
for (auto& kv: enumerate(my_vec)) {
kv.second = kv.first;
}
(sort of std::iota);
Second, making a copy of the value:
std::vector<int> a{10, 20, 30};
auto copy = *enumerate(a).begin();
a[0] = 15;
std::cout << copy.first << " " << copy.second; // 0 10
I'm confused what should be the return type of Iterator::operator*(). If it is std::pair<const int, T&> then in the second example value will not be copied. If it is std::pair<const int, T> then in the first example it is impossible to reference underlying values. What should I do and what should be value_type, reference and pointer typedefs of such iterator?
Here is my attempt to implement it. It supports taking references but not copying.
template<typename T>
struct Iterator {
using TT = typename std::iterator_traits<T>::value_type;
using value_type = std::pair<const int, TT>;
using reference = std::pair<const int&, typename std::iterator_traits<T>::reference>;
using pointer = value_type*;
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
std::pair<int, T> it;
Iterator(T iterator) : it(0, iterator) {}
bool operator==(const Iterator& other) const { return it.second == other.it.second; }
bool operator!=(const Iterator& other) const { return it.second != other.it.second; }
reference operator*() { return { it.first, *it.second }; }
Iterator& operator++() { ++it.first; ++it.second; return *this; }
};
P.S. I've just checked, boost::adaptors::index suffers from the same problem and does not copy the value.
This problem is similar to that of std::vector<bool>, you want to provide a proxy that acts just like a reference but also supports value semantics.
What's different though, is that the types involved aren't restricted, there is two references involved and all sorts of hairiness pops up. The following is a partial implementation and it illustrates some problems you run into
#include<iterator>
#include<functional>
template<typename F, typename S, bool defined = true>
struct sfinae_difference_type {};
template<typename F, typename S>
struct sfinae_difference_type<F, S,
std::is_same_v<typename std::iterator_traits<F>::difference_type,
typename std::iterator_traits<S>::difference_type>>
{
using difference_type = typename std::iterator_traits<F>::difference_type;
};
template<typename F, typename S>
class pair_iterator : sfinae_difference_type<F, S>
{
using Fvalue_type = typename std::iterator_traits<F>::value_type;
using Svalue_type = typename std::iterator_traits<S>::value_type;
using Freference = typename std::iterator_traits<F>::reference;
using Sreference = typename std::iterator_traits<S>::reference;
F f;
S s;
public:
using value_type = std::pair<Fvalue_type, Svalue_type>;
struct reference
{
Freference first;
Sreference second;
reference() = delete;
reference(const reference& other) : first{other.first}, second{other.second} {}
reference& operator=(const reference& rhs)
{
first = rhs.first;
second = rhs.second;
return *this;
}
operator value_type() { return {f, s}; }
private:
reference(Freference f, Sreference s) : first{f}, second{s} {}
friend pair_iterator;
};
struct pointer
{
// similar to reference
};
pair_iterator() = default;
pair_iterator(const pair_iterator&) = default;
pair_iterator(F f, S s) : f{f}, s{s} {}
pair_iterator& operator++() { ++f; ++s; return *this; }
reference operator*() { return {*f, *s}; }
pointer operator->() { return {f.operator->(), s.operator->()}; }
bool operator==(const pair_iterator& other)
{
return f == other.f && s == other.s;
}
};
You then use it as
#include<vector>
#include<list>
#include<iostream>
int main()
{
std::vector v{1, 2, 3, 4, 5};
std::list l{6, 7, 8, 9, 10};
pair_iterator begin{v.begin(), l.begin()}, end{v.end(), l.end()};
for(; begin != end; ++begin)
std::cout << begin->first << ' ' << begin->second << '\n';
}
Live
Some of the immediately obvious problems:
Implementation is tedious. Having sfinae friendly type aliases and proper proxies requires copious boilerplate.
The semantics of proxies may be confusing. What does copying/assigning one reference to another mean? What is auto is_this_a_copy = *it supposed to do?
What does equality mean? Does both internal iterators have to be equal to be equal? That breaks comparison with end iterators.
All of these have to be hammered out to make it work, and there isn't an easy answer.
Say I have a virtual base class Base, which will in part behave like a container, with two derived classes VectorLike and RangeLike.
I want to achieve something like the following:
class VectorLike : public Base {
std::vector<int> data;
public:
virtual std::vector<int>::const_iterator cbegin() { return data.cbegin() }
virtual std::vector<int>::const_iterator cend() { return data.cend() }
}
class RangeLike : public Base {
int min, max;
class const_iterator {
int x;
public:
int operator++() { return ++x }
bool operator==( const_iterator rhs ) { return x == rhs.x }
const_iterator( int y ) { x = y }
}
public:
virtual const_iterator cbegin() { return const_iterator( min ); }
virtual const_iterator cend() { return const_iterator( max ); }
}
This code will not compile, since std::vector<int>::const_iterator and RangeLike::const_iterator aren't identical or covariant.
To achieve the second, I would need an iterator base class from which both std::vector<int>::const_iterator and RangeLike::const_iterator will derive. But then still cbegin() and cend() will have to return pointers to iterators, which will make an even bigger mess.
My question is, is it possible to achieve something like the above code and if so how?
Here is an implementation of a polymorphic const int iterator. You can construct it with any iterator type (including pointers) where std::iterator_traits<Iter>::value_type resolves to int.
This should be the case for both std::vector<int> and your_range_type<int>.
This should get you started.
#include <iostream>
#include <vector>
#include <array>
#include <memory>
#include <algorithm>
struct poly_const_iterator
{
using value_type = int;
struct concept {
virtual void next(int n) = 0;
virtual const value_type& deref() const = 0;
virtual bool equal(const void* other) const = 0;
virtual std::unique_ptr<concept> clone() const = 0;
virtual const std::type_info& type() const = 0;
virtual const void* address() const = 0;
virtual ~concept() = default;
};
template<class Iter>
struct model : concept
{
model(Iter iter) : _iter(iter) {}
void next(int n) override { _iter = std::next(_iter, n); }
const value_type& deref() const override { return *_iter; }
bool equal(const void* rp) const override { return _iter == static_cast<const model*>(rp)->_iter; }
std::unique_ptr<concept> clone() const override { return std::make_unique<model>(*this); }
const std::type_info& type() const override { return typeid(_iter); }
const void* address() const override { return this; }
Iter _iter;
};
std::unique_ptr<concept> _impl;
public:
// interface
// todo: constrain Iter to be something that iterates value_type
template<class Iter>
poly_const_iterator(Iter iter) : _impl(std::make_unique<model<Iter>>(iter)) {};
poly_const_iterator(const poly_const_iterator& r) : _impl(r._impl->clone()) {};
const value_type& operator*() const {
return _impl->deref();
}
poly_const_iterator& operator++() {
_impl->next(1);
return *this;
}
bool operator==(const poly_const_iterator& r) const {
return _impl->type() == r._impl->type()
and _impl->equal(r._impl->address());
}
bool operator != (const poly_const_iterator& r) const {
return not(*this == r);
}
};
void emit(poly_const_iterator from, poly_const_iterator to)
{
std::copy(from, to, std::ostream_iterator<int>(std::cout, ", "));
std::cout << std::endl;
}
int main()
{
std::vector<int> v = { 1, 2, 3, 4, 5 };
std::array<int, 5> a = { 6, 7,8, 9, 0 };
emit(std::begin(v), std::end(v));
emit(std::begin(a), std::end(a));
return 0;
}
expected results:
1, 2, 3, 4, 5,
6, 7, 8, 9, 0,
Here's a C++20 based solution (partial implementation)... just thought I'd put it out there.
It uses a variant with stack storage rather than polymorphism and dynamic allocation, so it might have better performance.
It should work for any given set of iterator types that have a common value_type and whose reference type is value_type&. It should also be fairly easy to adapt for other types of iterator and to include operator->.
Not claiming this is better than the implementation in the original answer. Merely an interesting alternative...
PS. normal in the names below simply indicates a basic pointer based iterator.
template <typename _Iterator, typename _Value, typename _Reference, typename _Difference>
concept forward_iterator_for
= std::forward_iterator<_Iterator>
&& std::same_as<std::iter_value_t<_Iterator>, _Value>
&& std::same_as<std::iter_reference_t<_Iterator>, _Reference>
&& std::same_as<std::iter_difference_t<_Iterator>, _Difference>;
template <typename _Iterator, typename _Value>
concept normal_forward_iterator_for //
= std::same_as<std::remove_cvref_t<_Value>, std::remove_const_t<_Value>>
&& forward_iterator_for<_Iterator, std::remove_const_t<_Value>, _Value&, std::ptrdiff_t>;
template <typename _Value, normal_forward_iterator_for<_Value> ... _Iterator>
class normal_forward_iterator_variant {
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = std::remove_cv_t<_Value>;
using reference = _Value&;
using iterator_type = std::variant<_Iterator...>;
private:
iterator_type _m_iter;
public:
normal_forward_iterator_variant() = default;
template <normal_forward_iterator_for<_Value> _Iter, typename... _Args>
normal_forward_iterator_variant(std::in_place_type_t<_Iter>, _Args&&... args)
noexcept (std::is_nothrow_constructible_v<_Iter, _Args&&...>)
: _m_iter(std::in_place_type<_Iter>, std::forward<_Args>(args)...) {}
normal_forward_iterator_variant(iterator_type const& iter)
noexcept (std::is_nothrow_copy_constructible_v<iterator_type>)
: _m_iter(iter) {}
normal_forward_iterator_variant(normal_forward_iterator_variant const&) = default;
normal_forward_iterator_variant(normal_forward_iterator_variant&&) = default;
constexpr normal_forward_iterator_variant&
operator=(normal_forward_iterator_variant const& iter)
noexcept (std::is_nothrow_copy_assignable_v<iterator_type>) //
requires std::is_copy_assignable_v<iterator_type> {
_m_iter = iter._m_iter;
return *this;
}
constexpr normal_forward_iterator_variant&
operator=(normal_forward_iterator_variant&& iter)
noexcept (std::is_nothrow_move_assignable_v<iterator_type>) //
requires std::is_move_assignable_v<iterator_type> {
_m_iter = std::move(iter._m_iter);
return *this;
}
template <typename _Tp>
constexpr normal_forward_iterator_variant&
operator=(_Tp const& x)
noexcept (std::is_nothrow_assignable_v<iterator_type, _Tp&>) //
requires std::is_assignable_v<iterator_type, _Tp&> {
_m_iter = x;
return *this;
}
template <typename _Tp>
constexpr normal_forward_iterator_variant&
operator=(_Tp&& x)
noexcept (std::is_nothrow_assignable_v<iterator_type, _Tp&&>) //
requires std::is_assignable_v<iterator_type, _Tp&&> {
_m_iter = std::move(x);
return *this;
}
[[nodiscard]] constexpr reference
operator*() const noexcept {
return std::visit([](auto&& iter) -> reference {
return *iter;
}, _m_iter);
}
constexpr normal_forward_iterator_variant&
operator++() noexcept {
std::visit([](auto&& iter) {
++iter;
}, _m_iter);
return *this;
}
constexpr normal_forward_iterator_variant
operator++(int) noexcept {
normal_forward_iterator_variant rv(*this);
++*this;
return rv;
}
[[nodiscard]] friend constexpr bool
operator==(normal_forward_iterator_variant const& a, normal_forward_iterator_variant const& b) noexcept {
return (a._m_iter == b._m_iter);
}
};
Example use:
#include <iostream>
#include <iomanip>
#include <vector>
#include <list>
#include <iterator.h>
int
main() {
using vector = std::vector<int>;
using list = std::list<int>;
using iterator = normal_forward_iterator_variant<const int, vector::const_iterator, list::const_iterator>;
iterator iter;
vector v{ 0, 1, 2 };
iter = v.cbegin();
std::cout << *iter++ << std::endl;
std::cout << *iter << std::endl;
std::cout << *++iter << std::endl;
list l{ 3, 4, 5 };
iter = l.cbegin();
std::cout << *iter++ << std::endl;
std::cout << *iter << std::endl;
std::cout << *++iter << std::endl;
}
Output is 1, 2, ..., 6 as expected.
I am trying to write a class that should act as a sorted view on some underlying sequence of elements. So far I have come up with a non-const version. Now I have problems adapting it to also provide const_iterator functionality.
The code I have so far looks like this:
// forward declare iterator
template <class InputIt>
class sorted_range_iter;
template <class InputIt>
class sorted_range {
friend class sorted_range_iter<InputIt>;
private:
using T = typename InputIt::value_type;
InputIt _first;
InputIt _last;
std::vector<size_t> _indices;
public:
using iterator = sorted_range_iter<InputIt>;
sorted_range() = default;
sorted_range(InputIt first, InputIt last)
: _first(first), _last(last), _indices(std::distance(_first, _last)) {
std::iota(_indices.begin(), _indices.end(), 0);
};
template <class Compare = std::less<T>>
void sort(Compare comp = Compare()) {
std::sort(_indices.begin(), _indices.end(),
[this, &comp](size_t i1, size_t i2) {
return comp(*(_first + i1), *(_first + i2));
});
}
size_t size() const { return _indices.size(); }
T& operator[](size_t pos) { return *(_first + _indices[pos]); }
const T& operator[](size_t pos) const { return (*this)[pos]; }
iterator begin() { return iterator(0, this); }
iterator end() { return iterator(size(), this); }
};
And the corresponding iterator looks like this:
template <class InputIt>
class sorted_range_iter
: public std::iterator<std::forward_iterator_tag, InputIt> {
friend class sorted_range<InputIt>;
private:
using T = typename InputIt::value_type;
size_t _index;
sorted_range<InputIt>* _range;
sorted_range_iter(size_t index, sorted_range<InputIt>* range)
: _index(index), _range(range) {}
public:
T& operator*() { return *(_range->_first + _range->_indices[_index]); }
// pre-increment
const sorted_range_iter<InputIt>& operator++() {
_index++;
return *this;
}
// post-increment
sorted_range_iter<InputIt> operator++(int) {
sorted_range_iter<InputIt> result = *this;
++(*this);
return result;
}
bool operator!=(const sorted_range_iter<InputIt>& other) const {
return _index != other._index;
}
};
An usage example looks like this:
std::vector<int> t{5, 2, 3, 4};
auto rit = ref.begin();
sorted_range<std::vector<int>::iterator> r(begin(t), end(t));
r.sort();
for(auto& x : r)
{
std::cout << x << std::endl;
}
Output:
2
3
4
5
How do I adapt my iterator for the const case? It would be easier if the iterator would be templated on the underlying type (int for example) instead InputIt. Is there a better way to define this class?
I suppose one could solve this for example by using the range-v3 library, however I am trying to not add any more dependencies and rely on C++11/14 functions.
You just are using the wrong type for T. You have:
using T = typename InputIt::value_type;
But value_type is the same for iterator and const_iterator. What they have are different reference types. You should prefer:
using R = typename std::iterator_traits<InputIt>::reference;
R operator*() { ... }
This has the added benefit of working with pointers.
Alternatively, could avoid iterator_traits by just trying to dereference the iterator itself:
using R = decltype(*std::declval<InputIt>());
Side-note, shouldn't sorted_range sort itself on construction? Otherwise, easy to misuse.