Why does implicit conversion to constant iterator fail in this code? - c++

This relates to my previous question here.
Here's a summary of that thread: I am trying to implement a doubly-linked-list class called my_list in C++, including iterators and I want automatic implicit conversion of iterator to const_iterator.
My original thought was to have something like
template<class T>
class my_list_iterator<T>
{
node<T> pos_;
/* code */
operator my_list_iterator<const T>(){return my_list_iterator<const T>(pos_);}
};
and then inside my_list define iterator as my_list_iterator<T> and const_iterator as my_list_iterator<const T>.
However, as pointed out by Miled Budneck in the previous thread, this won't work because a pointer to node<T> cannot convert to a pointer to node<const T>. He suggested I re-implement the const iterator as a pointer to const node instead.
While that works, by looking around I also found one other possible way to define the iterator class:
template<class T, class Pointer, class Reference>
class my_list_iterator {
node<T>* pos_;
/* code */
operator my_list_iterator<T,const Pointer,const Reference>() {return my_list_iterator<T,const Pointer,const Reference>(pos_);}
};
Then I can define my_list<T>::iterator as my_list_iterator<T,T*,T&> and my_list<T>::const_iterator as my_list_iterator<T,const T*,const T&>. I thought the last line would take care of the implicit conversion issue, but it seems to not be used at all.
For reference, here is the complete code:
template<class T> class node {
node(const T& t = T()):data(t),next(0),prev(0) {}
T data;
node* next;
node* prev;
friend class my_list<T>;
template<class U,class Pointer,class Reference> friend class my_list_iterator;
};
template<class T,class Pointer,class Reference> class my_list_iterator {
public:
// increment and decrement operators
my_list_iterator operator++();
my_list_iterator operator++(int);
my_list_iterator operator--();
my_list_iterator operator--(int);
// bool comparison iterators
bool operator==(const my_list_iterator& other) const {return pos_==other.pos_;}
bool operator!=(const my_list_iterator& other) const {return pos_!=other.pos_;}
// member access
Reference operator*() const {return pos_->data;}
Pointer operator->() const {return &(pos_->data);}
// conversion to constant
operator my_list_iterator<T,const Pointer,const Reference>() {return pos_;}
private:
node<T>* pos_;
explicit my_list_iterator(node<T>* p=0):pos_(p) {}
friend class my_list<T>;
};
and the error message in the end
note: no known conversion for argument 1 from ‘my_list<int>::iterator’ {aka ‘my_list_iterator<int, int*, int&>’} to ‘const my_list_iterator<int, const int*, const int&>&’
So why does this code not work? Is there any small modification I can make to make it work, or is this design impossible to implement?

Given Pointer = T*, const Pointer is T* const, not const T*. Type substitution is not like macro expansion. const Pointer adds a top-level const to the type denoted by Pointer. Similarly, const Reference is T& const (which is automatically adjusted to T& because top-level cv-qualifiers on references are ignored) instead of const T&. (See What is the difference between const int*, const int * const, and int const *? for the difference between T* const and const T*.)
You need to use something like const std::remove_pointer<Pointer>* for this to work.

Related

In C++20, how do I write a contiguous iterator?

C++20 has explicit library support for std::contiguous_iterator_tag. Some STL algorithms (e.g. std::copy) can perform much better on contiguous iterators. However, I'm unclear on exactly how the programmer is supposed to get access to this new functionality.
Let's suppose for the sake of argument that we have a completely conforming C++20 library implementation. And I want to write the simplest possible contiguous iterator.
Here's my first attempt.
#include <iterator>
class MyIterator {
int *p_;
public:
using value_type = int;
using reference = int&;
using pointer = int*;
using difference_type = int;
using iterator_category = std::contiguous_iterator_tag;
int *operator->() const;
int& operator*() const;
int& operator[](int) const;
MyIterator& operator++();
MyIterator operator++(int);
MyIterator& operator--();
MyIterator operator--(int);
MyIterator& operator+=(int);
MyIterator& operator-=(int);
friend auto operator<=>(MyIterator, MyIterator) = default;
friend int operator-(MyIterator, MyIterator);
friend MyIterator operator+(MyIterator, int);
friend MyIterator operator-(MyIterator, int);
friend MyIterator operator+(int, MyIterator);
};
namespace std {
int *to_address(MyIterator it) {
return it.operator->();
}
}
static_assert(std::contiguous_iterator<MyIterator>); // FAILS
This fails on both GCC/libstdc++ and MSVC/STL; but is it supposed to?
For my next attempt, I specialized pointer_traits<MyIterator>. MyIterator isn't actually a pointer, so I didn't put anything in pointer_traits except the one function that the library needs. This is my second attempt:
#include <iterator>
class MyIterator {
~~~
};
template<>
struct std::pointer_traits<MyIterator> {
int *to_address(MyIterator it) {
return it.operator->();
}
};
static_assert(std::contiguous_iterator<MyIterator>); // OK!
Is this how I'm supposed to do it? It feels extremely hacky. (To be clear: my first failed attempt also feels extremely hacky.)
Am I missing some simpler way?
In particular, is there any way for MyIterator itself to warrant that it's contiguous, just using members and friends and stuff that can be defined right there in the body of the class? Like, if MyIterator is defined in some deeply nested namespace, and I don't want to break all the way out to the top namespace in order to open namespace std.
EDITED TO ADD: Glen Fernandes informs me that there is a simpler way — I should just add an element_type typedef, like this! (And I can remove 3 of iterator_traits' big 5 typedefs in C++20.) This is looking nicer!
#include <iterator>
class MyIterator {
int *p_;
public:
using value_type = int;
using element_type = int;
using iterator_category = std::contiguous_iterator_tag;
int *operator->() const;
int& operator*() const;
int& operator[](int) const;
MyIterator& operator++();
MyIterator operator++(int);
MyIterator& operator--();
MyIterator operator--(int);
MyIterator& operator+=(int);
MyIterator& operator-=(int);
friend auto operator<=>(MyIterator, MyIterator) = default;
friend int operator-(MyIterator, MyIterator);
friend MyIterator operator+(MyIterator, int);
friend MyIterator operator-(MyIterator, int);
friend MyIterator operator+(int, MyIterator);
};
static_assert(std::contiguous_iterator<MyIterator>); // OK!
Also, I've seen some stuff about using member typedef iterator_concept instead of iterator_category. Why might I ever want to provide MyIterator::iterator_concept? (I'm not asking for a full historical explanation here; just a simple best-practice guideline "nah forget about iterator_concept" or "yes provide it because it helps with X" would be nice.)
There are two ways to support std::to_address. One is:
namespace std {
template<>
struct pointer_traits<I> {
static X* to_address(const I& i) {
// ...
}
};
}
Note the static above.
The second way, as Glen pointed out to you, is to simply define I::operator-> and to make sure the primary template of std::pointer_traits<I> is valid. This just requires that std::pointer_traits<I>::element_type is valid.
The answer by Nicol Bolas is incorrect because that is not how you customize std::to_address for a user-defined type. Since C++20 (because of P0551) it is forbidden to specialize function templates in namespace std that you're not explicitly allowed to.
You're not allowed to specialize std::to_address. You can provide std::pointer_traits<I>::to_address though, and std::to_address will call it if it exists.
The last version in my question seems to be the most correct. There is just one subtlety to watch out for:
MyIterator::value_type should be the cv-unqualified type of the pointee, such that someone could write value_type x; x = *it;
MyIterator::element_type should be the cv-qualified type of the pointee, such that someone could write element_type *ptr = std::to_address(it);
So for a const iterator, element_type isn't just a synonym for value_type — it's a synonym for const value_type! If you don't do this, things will explode inside the standard library.
Here's a Godbolt proof-of-concept. (It's not a complete ecosystem, in that really you'd want MyIterator to be implicitly convertible to MyConstIterator, and probably use templates to eliminate some of the repetition. I have a rambly blog post on that subject here.)
#include <iterator>
class MyIterator {
int *p_;
public:
using value_type = int;
using element_type = int;
using iterator_category = std::contiguous_iterator_tag;
int *operator->() const;
int& operator*() const;
int& operator[](int) const;
MyIterator& operator++();
MyIterator operator++(int);
MyIterator& operator--();
MyIterator operator--(int);
MyIterator& operator+=(int);
MyIterator& operator-=(int);
friend auto operator<=>(MyIterator, MyIterator) = default;
friend int operator-(MyIterator, MyIterator);
friend MyIterator operator+(MyIterator, int);
friend MyIterator operator-(MyIterator, int);
friend MyIterator operator+(int, MyIterator);
};
static_assert(std::contiguous_iterator<MyIterator>); // OK!
class MyConstIterator {
const int *p_;
public:
using value_type = int;
using element_type = const int;
using iterator_category = std::contiguous_iterator_tag;
const int *operator->() const;
const int& operator*() const;
const int& operator[](int) const;
MyConstIterator& operator++();
MyConstIterator operator++(int);
MyConstIterator& operator--();
MyConstIterator operator--(int);
MyConstIterator& operator+=(int);
MyConstIterator& operator-=(int);
friend auto operator<=>(MyConstIterator, MyConstIterator) = default;
friend int operator-(MyConstIterator, MyConstIterator);
friend MyConstIterator operator+(MyConstIterator, int);
friend MyConstIterator operator-(MyConstIterator, int);
friend MyConstIterator operator+(int, MyConstIterator);
};
static_assert(std::contiguous_iterator<MyConstIterator>); // OK!
One of the main benefits of C++ concepts as a language feature is that the concept definition tells you what you need to provide. std::contiguous_iterator is no different. Yes, you may have to dig through 10+ layers of other concept definitions to get down to the basic terms you need to provide. But they are right there in the language.
To whit, a contiguous iterator is a random access iterator:
Whose tag is derived from contiguous_iterator
Whose reference_type is an lvalue reference to its value_type (ie: not a proxy iterator or generator).
For which the standard library function std::to_address can be called to convert any valid iterator (even one which is not dereferencable) into a pointer either to the element the iterator references or to the one-past-the-end pointer.
Unfortunately, satisfying #3 can't be done by specializing std::to_address directly. You have to use either specialize pointer_traits::to_address for your iterator type or give your iterator an operator-> overload.
The latter is easier to do and, despite operator-> not being otherwise required by std::contiguous_iterator, makes sense for such an iterator type:
class MyIterator
{
...
int const *operator->() const;
};
FYI: Most compilers will give you more helpful error messages if you constrain a template based on the concept, rather than just static_asserting on it. Like this:
template<std::contiguous_iterator T>
void test(const T &t);
test(MyIterator{});

Why can I update member variables in a const member function?

I'm trying to implement a linked list similar to how it might be implemented in the STL. While implementing the iterator, I made some const member functions (so the user can make use of a const iterator) and noticed that I was able to update member variables without getting a compiler error. The code uses templates, but I tested it calling a function that uses begin() and with a const list, so I know that the template functions that modify the member variables were generated by the compiler. Does anyone know why this works? The function in question is the const version of operator++.
Here's a version of my program, with irrelevant details taken out.
template<typename E>
struct Link {
E val {};
Link* next = nullptr;
Link* prev = nullptr;
};
template<typename E>
struct List {
struct Iterator {
Iterator(Link<E>* c) : curr{c} { }
Iterator& operator++();
const Iterator& operator++() const;
const E& operator*() const {return curr->val;}
E& operator*() {return curr->val;}
// ...
private:
Link<E>* curr;
};
// Constructors, etc ...
// Operations ...
E& front() {return head->val;}
const E& front() const {return head->val;}
Iterator begin() {return Iterator{head};}
const Iterator begin() const {return Iterator{head};}
// Other iterator stuff ...
private:
Link<E>* head;
Link<E>* tail;
int sz;
};
/*---------------------------------------------*/
template<typename E>
typename List<E>::Iterator& List<E>::Iterator::operator++() {
curr = curr->next;
return *this;
}
template<typename E>
const typename List<E>::Iterator&
List<E>::Iterator::operator++() const
{
curr = curr->next;
return *this;
}
I think conceptually, it makes sense to make a const version of operator++ even though it modifies member variables. A const iterator actually refers to the contents of the Link pointer being const, which is exactly why it returns a const E& in the dereference operator. Thus, with a const iterator, you can never update the contents of the iterator.
Let me know if there's anything I should include in the code snippet, thank you!
Template functions aren't actually checked for errors until they're instantiated. If you don't call them they just sit there unnoticed, bombs waiting to go off. You'll get a compiler error once you add a call to Iterator::operator++() const.
For example, I added:
int main() {
List<int> list;
const List<int>::Iterator iter = list.begin();
++iter;
}
And now clang complains:
main.cpp:52:10: error: cannot assign to non-static data member within const
member function 'operator++'
curr = curr->next;
~~~~ ^
main.cpp:61:3: note: in instantiation of member function
'List<int>::Iterator::operator++' requested here
++iter;
^
main.cpp:14:25: note: member function 'List<int>::Iterator::operator++' is
declared const here
const Iterator& operator++() const;
^
(Repl)
I think conceptually, it makes sense to make a const version of operator++ even though it modifies member variables. A const iterator actually refers to the contents of the Link pointer being const, which is exactly why it returns a const E& in the dereference operator. Thus, with a const iterator, you can never update the contents of the iterator.
A const iterator should not be mutable, nor have a ++ operator. The STL actually has separate iterator and const_iterator types. A const_iterator embodies the concept you're describing: the iterator is itself mutable, but what it points to is const.
I recommend you follow suit and create a separate ConstIterator class.

Is it possible to reuse usual iterator to build const iterator?

Research
I found this old answer. I'd like to know if the solution is still effective or if there is a new, more effective way to do it.
Background
Lets suppose I have an iterator like below (specifics don't really matter, only that it is huge):
class inorder_iterator : public std::iterator<std::forward_iterator_tag, token>
{
friend syntax_tree;
node* current_node;
std::stack<node*> prev_nodes;
//std::stack<node*> visited_nodes;
std::map<node*, bool> visited;
public:
inorder_iterator();
inorder_iterator& operator++();
inorder_iterator operator++(int);
token& operator*();
const token& operator*() const;
token* operator->();
const token* operator->() const;
friend bool operator==(const inorder_iterator lhs, const inorder_iterator rhs);
friend bool operator!=(const inorder_iterator lhs, const inorder_iterator rhs);
private:
inorder_iterator(node* current);
node* find_leftmost_node(node* from);
};
Problem
Implementations of the member function declarations are of reasonable size, but I would like to reuse the current iterator to reduce code duplication.
Idea
First idea that came to mind is to templatize on node type, so I could pass const node to make it const iterator, but it just sounds fishy
template <typename Node>
//replace every occurrence of node with Node
// and use decltype(node.tk) instead of token everywhere
Also I'm not sure if this use of const is one of those "const belongs to implementation specifics" case.
A template is probably the only way to avoid code duplication. But I wouldn't leave the type parameter open. I'd simply use a boolean parameter that is fed to a std::conditional in order to determine the type:
template<bool IsConst> class iter_impl {
using value_type = std::conditional_t<IsConst, Node const, Node>;
};
The two iterator types of the container can then be either a couple of aliases, or if you want truly distinct types, a couple of classes that inherit from the template. Like so:
struct const_iterator : iter_impl<true> {};
struct iterator : iter_impl<false> {};
The benefit of using two new classes, is that you can define a converting constructor for const_iterator that allows it to be build from the non const iterator. This is akin to the standard library's behavior.
Also I'm not sure if this use of const is one of those "const belongs to implementation specifics" case.
The fact you use a const Node is indeed an implementation detail. But so long as it gives you the documented type behavior (an iterator to a const member of the container) I wouldn't stress too much over this.
You can safely define class template template< typename value_type > iterator parametrized by value type (const or non-const) with conversion operator to const version (i.e. operator interator< value_type const > () const):
template< typename type >
struct node
: node_base
{
union { type value; };
node() noexcept { ; }
node(node const &) = delete;
node(node &&) = delete;
void operator = (node const &) = delete;
void operator = (node &&) = delete;
~node() { ; }
type * pointer() noexcept { return &value; }
};
template< typename type >
struct tree_iterator
{
using value_type = type;
using reference = type &;
using pointer = type *;
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = std::ptrdiff_t;
using node_type = node< value_type >;
using node_pointer = node_type *;
base_pointer p = nullptr;
pointer operator -> () const noexcept { return node_pointer(p)->pointer(); }
reference operator * () const noexcept { return *operator -> (); }
tree_iterator & operator ++ () noexcept { p = increment(p); return *this; }
tree_iterator operator ++ (int) noexcept { return {std::exchange(p, increment(p))}; }
tree_iterator & operator -- () noexcept { p = decrement(p); return *this; }
tree_iterator operator -- (int) noexcept { return {std::exchange(p, decrement(p))}; }
bool operator == (tree_iterator const & it) const noexcept { return p == it.p; }
bool operator != (tree_iterator const & it) const noexcept { return !operator == (it); }
operator tree_iterator< type const > () const { return {p}; }
};
In using const_iterator = iterator< value_type const >; the latter became no-op and can be called only intentionally (i.e. using syntax it.template operator iterator< value_type const > ()).
The code is given from here - rewrite of libstdc++'s red-black tree.

When is it sufficient to declare const_iterator as a const iterator?

For example, I could define a container in a way similar to:
template <typename T>
class Example
{
public:
using value_type = T;
using iterator = value_type*;
using const_iterator = const iterator;
//etc
};
However, is it okay to do this with a user-defined iterator?
template<typename T>
class Example
{
public:
using value_type = T;
/*friend?*/ class iterator;
using const_iterator = const iterator; //is this okay?
//etc
};
A detailed explanation would be appreciated.
A const_iterator and a const iterator represent two different things. In each case, the const applies to something different: the object "pointed to" by the iterator in the first case, and the iterator itself in the second. So, it is never "sufficient" to use one for the other.
In pointer speak (and pointers are iterators), this would be the difference between "pointer to const" and "const pointer".
const int* it; // pointer to const int: a const_iterator
int* it const; // const pointer to int: a const iterator
You can even have const iterators to const, or const const_iterator:
const int* it const;

Why I got an incomplete type error and how to solve it?

This is a code a got for now but it doesnt compile due to an incomplete variable type container::iterator error. The class and the iterator are still pretty simple since I am trying to figure out how to organise my code.
template <typename T> class container {
using size_type = std::size_t;
using pointer = T*;
using const_pointer = const T*;
public:
class iterator;
container (size_type n=0): data {n>0 ? new T[n]:nullptr}, size {n>0 ? n:0}{};
container (iterator begin, iterator end){};
~container (){ delete[] data; };
private:
pointer data;
size_type size;
};
template <typename T> class container<T>::iterator {
public:
iterator (pointer p): current {p} {};
iterator& operator++ (){ current++; return *this; };
iterator& operator-- (){ current--; return *this; };
reference operator* (){ return *current; };
bool operator== (const iterator& b) const { return current == b.current; };
bool operator!= (const iterator& b) const { return current != b.current; };
private:
pointer current;
};
I would like to keep the iterator definition out of the container class if possible. Thanks for any replies.
container (iterator begin, iterator end){};
That uses iterator but at that point in the code there's no definition of iterator, only a declaration. You'll need to define the structure of your iterator before you can even declare this constructor.
You could alter this constructor to take references. Then you can declare it within your class but then define it below the definition for iterator. IMO this isn't the best way to go, but it's possible.
I would like to keep the iterator definition out of the container class if possible.
It isn't, because the arguments for container's member functions cannot be properly analysed without a definition for their type.
Move it inline.