I'm trying to write a wrapper class that interprets a std::vector<T> as a mapping of integers to Ts. As I want it to behave like a map, its iterators should dereference to (key, value) pairs. Since there are no such pairs in memory, the reference type of my corresponding iterator class is a bit unconventional:
using value_type = std::pair<Key, Value>;
using reference = std::pair<Key, Value&>;
For the corresponding const_iterator, this becomes
using value_type = std::pair<Key, const Value>;
using reference = std::pair<Key, const Value&>;
Now, however, my const_iterator no longer satisfies std::indirectly_readable (and, thus, std::forward_iterator and, thus, std::random_access_iterator) because
no type named 'type' in 'struct std::common_reference<std::pair<long unsigned int, const int&>&&, std::pair<long unsigned int, const int>&>'
(see https://godbolt.org/z/s89z56rY7) or the full code here:
#include <iostream>
#include <type_traits>
#include <vector>
#include <iterator>
using namespace std;
template<class Value, class VType, class RType>
struct _vector_map_iterator {
Value* _data = nullptr;
size_t index = 0;
using value_type = VType;
using reference = RType;
using difference_type = ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
reference operator*() const { return {index, *_data}; }
_vector_map_iterator& operator++() { ++index; return *this; }
_vector_map_iterator operator++(int) {_vector_map_iterator res = *this; ++(*this); return res; }
bool operator==(const _vector_map_iterator& other) const { return _data == other.data; }
};
template<class T>
using vmap_iterator = _vector_map_iterator<T, pair<size_t, T>, pair<size_t, T&>>;
template<class T>
using vmap_const_iterator = _vector_map_iterator<const T, pair<size_t, const T>, pair<size_t, const T&>>;
using I = vmap_const_iterator<int>;
//static_assert(std::common_reference_with<typename I::reference&&, typename I::value_type&>);
static_assert(forward_iterator<I>);
template<class Value>
struct vector_map {
vector<Value> _raw_data;
using iterator = vmap_iterator<Value>;
using const_iterator = vmap_const_iterator<Value>;
iterator begin() { return {_raw_data.data(), 0}; }
const_iterator begin() const { return {_raw_data.data(), 0}; }
vector_map(const initializer_list<Value>& lst): _raw_data(lst) {};
};
int main(){
const vector_map<int> int_map = {1,2,3};
const auto it = int_map.begin();
cout << (*it).first << ": " << (*it).second << '\n';
}
My question would be: is there a sensible (I specifically do not want to store key-value-pairs in the vector!) design for such an iterator class that also adheres to std::random_access_iterator?
EDIT: more details + full example
Related
I'm writing a custom vector class:
#include <iterator>
template <typename T>
class vector {
public:
using value_type = T;
using pointer = value_type*;
using iterator = pointer;
using const_iterator = const iterator;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
auto crbegin() const -> const_reverse_iterator {
return const_reverse_iterator(data_);
}
private:
pointer data_{};
};
int main(int argc, char* argv[]) {
vector<int> v;
auto i = v.crbegin();
return 0;
}
When compiling the code above, I get this error (on GCC and MSVC):
error: no type named ‘iterator_category’ in ‘struct std::iterator_traits<int* const>’
The error goes away when I change the reverse iterator definition to std::reverse_iterator<const T*>. What's the difference compared to std::reverse_iterator<const_iterator>?
The problem can be reduced to this:
#include <iterator>
template <typename T>
class vector {
public:
using value_type = T;
using pointer = value_type*;
using const_reverse_iterator = std::reverse_iterator<const pointer>;
auto crbegin() const -> const_reverse_iterator {
return const_reverse_iterator(data_);
}
private:
pointer data_{};
};
int main() {
vector<int> v;
auto i = v.crbegin();
(void) i;
return 0;
}
And the error message from clang makes the nature of the problem clearer:
no type named 'reference' in 'std::iterator_traits<int *const>'
^^^^^^^^^^
And so we see that what you thought was a const *int (i.e. the pointed-to object is const) is in fact an int *const (i.e the pointer itself is const).
Here's a simple fix:
#include <iterator>
template <typename T>
class vector {
public:
using value_type = T;
using pointer = value_type*;
using const_pointer = const value_type*;
using const_reverse_iterator = std::reverse_iterator<const_pointer>;
auto crbegin() const -> const_reverse_iterator {
return const_reverse_iterator(data_);
}
private:
pointer data_{};
};
int main() {
vector<int> v;
auto i = v.crbegin();
(void) i;
return 0;
}
Live demo
In "Mastering the C++17 STL" book I saw both iterator and const_iterator implementation in one class using conditional for less code duplication
Here's my implementation for simple array class (most code for array class is skipped):
template<class T, size_t N>
class Array
{
public:
template<bool Const>
class ArrayIterator {
friend class Array;
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = std::conditional<Const, const value_type*, value_type*>;
using reference = std::conditional<Const, const value_type&, value_type&>;
using iterator_category = std::random_access_iterator_tag;
reference operator*() const { return *ptr; }
ArrayIterator<Const>& operator++() { ++ptr; return *this; }
ArrayIterator<Const> operator++(int) { auto res = *this; ++(*this); return res; }
template<bool R>
bool operator==(const ArrayIterator<R>& iter) const { return ptr == iter.ptr; }
template<bool R>
bool operator!=(const ArrayIterator<R>& iter) const { return ptr != iter.ptr; }
private:
explicit ArrayIterator(pointer p) : ptr(p) {};
pointer ptr;
};
using iterator = ArrayIterator<false>;
using const_iterator = ArrayIterator<true>;
iterator begin() { return iterator(data); }
iterator end() { return iterator(data + N); }
const_iterator cbegin() const { return const_iterator(data); }
const_iterator cend() const { return const_iterator(data + N); }
private:
T* data;
};
This code compiles with no errors, but iterator is kinda unusable:
Array<int, 100> arr;
/*filling it with numbers*/
int x = *arr.begin();
Gives error:
main.cpp:9:9: error: no viable conversion from 'Array<int, 100>::ArrayIterator<false>::reference' (aka 'conditional<false, const int &, int &>') to 'int'
How can I use that iterator or should I just abandon this idea from book?
The member type pointer and reference of ArrayIterator should be defined as member type type of std::conditional, not std::conditional itself.
Change them to:
using pointer = typename std::conditional<Const, const value_type*, value_type*>::type;
// ^^^^^^^^ ^^^^^^
using reference = typename std::conditional<Const, const value_type&, value_type&>::type;
// ^^^^^^^^ ^^^^^^
Or (since C++14)
using pointer = std::conditional_t<Const, const value_type*, value_type*>;
// ^^
using reference = std::conditional_t<Const, const value_type&, value_type&>;
// ^^
Let me start by giving you a basic associative-array implementation.
#include <utility>
#include <functional>
#include <vector>
#include <algorithm>
#include <iostream>
namespace chops {
template <typename Key, typename Value,
typename Compare = std::less<Key>>
struct map {
using value_type = std::pair<const Key, Value>;
using key_type = Key;
using mapped_type = Value;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using key_compare = Compare;
using reference = value_type&;
using const_reference = value_type const&;
using iterator = typename std::vector<value_type>::iterator;
using const_iterator = typename std::vector<value_type>::const_iterator;
iterator begin() {
return data.begin();
}
iterator end() {
return data.end();
}
const_iterator begin() const {
return data.begin();
}
const_iterator end() const {
return data.end();
}
const_iterator cbegin() const {
return data.cbegin();
}
const_iterator cend() const {
return data.cend();
}
size_type size() {
return data.size();
}
bool empty() {
return data.empty();
}
void clear() {
data.clear();
}
mapped_type& operator[](Key const& k) {
key_comp kc{k};
iterator it = std::find_if(begin(), end(), kc);
if(it == end()) {
auto n = std::make_pair(k, mapped_type{});
data.push_back(n);
return data.back().second;
} else
return (*it).second;
}
template< class... Args >
std::pair<iterator,bool> emplace(Args&&... args) {
value_type v{std::forward<Args>(args)...};
key_comp kc{v.first};
iterator it = std::find_if(begin(), end(), kc);
if(it == end()) {
data.push_back(v);
return std::make_pair(data.end()--, true);
} else
return std::make_pair(it, false);
}
void erase(iterator pos){
data.erase(pos);
}
private:
struct key_comp {
Key k;
bool operator()(value_type const& p1) {
return p1.first == k;
}
};
std::vector<value_type> data;
};
}
int main() {
chops::map<int, std::string> m1;
m1[1] = "Burak";
m1.emplace(2, "Kaan");
m1.emplace(3, "Copur");
for (auto& kv : m1)
std::cout << kv.first << " has value " << kv.second << std::endl;
m1.erase(m1.begin());
for (auto& kv : m1)
std::cout << kv.first << " has value " << kv.second << std::endl;
}
First, the code uses vector to store key-value pairs. This makes implementation easy but operations are not O(log(n)) like balanced-tree implementations or O(1) like hashing-based implementations. In fact, this is a container of containers in a sense and the outer container may be parametrized in an implementation. Anyways, my problem is about that erase function, which I just delegate to erase function of underlying vector. Compiler tells me that copy constructor is removed from the pair type, since it has a user-defined move. Erase tries to copy the parts after the erase place I guess, and tries to use copy-constructor there.
The thing is, I devised a minimal example as follows:
#include <vector>
#include <string>
#include <utility>
int main() {
auto p1 = std::make_pair(1, "Burak");
auto p2 = std::make_pair(2, "Kaan");
std::vector<std::pair<int, std::string>> v1;
v1.push_back(p1);
v1.push_back(p2);
v1.erase(v1.begin());
}
where I can easily erase from vectors. So, what might be not working in my erase implementation?
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.
I was wondering if there is an iterator in the STL that dereferences the object pointed before returning it. This could be very useful when manipulating containers aggregating pointers. Here's an example of what I would like to be able to do:
#include <vector>
#include <iterator>
#include <algorithm>
using namespace std;
int main()
{
vector<int*> vec;
int i = 1;
int j = 2;
int k = 3;
vec.push_back(&i);
vec.push_back(&j);
vec.push_back(&k);
copy(deref_iterator(vec.begin()),
deref_iterator(vec.end()),
ostream_iterator<int>(cout, " ")); // prints "1 2 3"
return 0;
}
Try Boost's indirect_iterator.
An indirect_iterator has the same category as the iterator it is wrapping. For example, an indirect_iterator<int**> is a random access iterator.
Assuming your actual use case is a bit more complex than a container of integer pointers!
You could check out the boost ptr containers
http://www.boost.org/doc/libs/1_35_0/libs/ptr_container/doc/reference.html
The containers contain dynamically allocated objects (ie pointers).
But all access to the objects (direct or via iterator) returns a reference to the object.
#include <boost/ptr_container/ptr_vector.hpp>
#include <iostream>
#include <iterator>
#include <algorithm>
using namespace std;
int main()
{
boost::ptr_vector<int> vec;
vec.push_back(new int(1));
vec.push_back(new int(2));
vec.push_back(new int(3));
copy(vec.begin(),vec.end(),
ostream_iterator<int>(std::cout, " ")); // prints "1 2 3 "
return 0;
}
If it is impossible using Boost, writing a custom iterator is not that hard. Here is an example of a "dereference iterator" that meets the InputIterator requirements :
#include <iterator>
template <typename T>
struct PointedType;
template <typename T>
struct PointedType<T*>
{
typedef T value_type;
};
template <typename InputIterator>
struct DerefIterator
{
typedef input_iterator_tag iterator_category;
typedef typename PointedType<
typename iterator_traits<InputIterator>::value_type>::value_type
value_type;
typedef typename iterator_traits<InputIterator>::difference_type
difference_type;
typedef value_type* pointer;
typedef value_type& reference;
public:
explicit DerefIterator(const InputIterator& ii)
: it(ii) {}
// Returns the object pointed by the object referenced by it
reference operator*() const { return **it; }
pointer operator->() const { return *it; }
DerefIterator& operator++()
{
++it;
return *this;
}
DerefIterator operator++(int)
{
DerefIterator tmp = *this;
++it;
return tmp;
}
bool equals(const DerefIterator<InputIterator> & di) const
{
return di.it == it;
}
private:
InputIterator it;
};
// Equality functions
template <typename InputIterator>
inline bool operator==(const DerefIterator<InputIterator>& di1,
const DerefIterator<InputIterator>& di2)
{
return di1.equals(di2);
}
template <typename InputIterator>
inline bool operator!=(const DerefIterator<InputIterator>& di1,
const DerefIterator<InputIterator>& di2)
{
return ! (di1 == di2);
}
//Helper function
template <typename InputIterator>
DerefIterator<InputIterator> deref_iterator(const InputIterator& ii)
{
return DerefIterator<InputIterator>(ii);
}