Questions about operator overloading - c++

I have two questions about operator overloading.
For an iterator type, how is operator-> overloaded? What value should it return assuming that it is an iterator for a collection of class T objects?
Why does operator++() return by class T& while operator++(int) return by class T? I understand these two represent prefix increment and postfix increment. But why the difference in return value?
EDIT: For Alf. Code is not complete though functioning. Any suggestions for improvement are welcome.
#ifndef DHASH_H
#define DHASH_H
//#include <vector>
#include <memory>
#include <exception>
#include <new>
#include <algorithm>
#include <functional>
namespace MCol
{
template <typename KEY, typename VALUE, typename HASH_FUNCTION, typename KEY_COMP = std::equal_to<KEY> >
class hash_container
{
private:
struct entry
{
KEY _k;
VALUE _v;
entry(const KEY& k, const VALUE& v)
:_k(k), _v(v)
{}
entry& operator=(const entry& e)
{
this->_k = e._k;
this->_v = e._v;
}
};
private:
struct bucket
{
entry* a_Entries;
size_t sz_EntryCount;
bucket()
{
sz_EntryCount = 0;
a_Entries = NULL;
}
~bucket()
{
for(size_t szI = 0; szI < sz_EntryCount; ++szI)
{
a_Entries[szI].~entry();
}
free(a_Entries);
}
//Grow by 1. (Perhaps later try block increment. But wikipedia suggests that there is little difference between the two)
inline bool insert(const KEY& k, const VALUE& v) throw (std::bad_alloc)
{
if(find(k) != NULL)
{
return false;
}
a_Entries = static_cast<entry*>(realloc(a_Entries, sizeof(entry)*(++sz_EntryCount)));
if(a_Entries == NULL)
{
throw std::bad_alloc();
}
new (&a_Entries[sz_EntryCount - 1]) entry(k, v);
return true;
}
//Find entry, swap with last valid entry, remove if necessary.
inline bool erase(const KEY& k) throw(std::bad_alloc)
{
//Forwards or backwards? My guess is backwards is better.
entry* pE = a_Entries;
while(pE != a_Entries + sz_EntryCount)
{
if(pE->_k == k)
{
break;
}
++pE;
}
if(pE == a_Entries + sz_EntryCount)
{
return false;
}
//We don't need to swap if the entry is the only one in the bucket or if it is the last one.
entry* pLast = a_Entries + sz_EntryCount - 1;
if((sz_EntryCount > 1) && (pE != pLast))
{
pE = pLast;
}
a_Entries = static_cast<entry*>(realloc(a_Entries, sizeof(entry)*(--sz_EntryCount)));
if(a_Entries == NULL && sz_EntryCount > 0)
{
throw std::bad_alloc();
}
return true;
}
inline entry* find(const KEY& k) throw()
{
//Better implement a search policy.
entry* pE = a_Entries;
while(pE != a_Entries + sz_EntryCount)
{
if(pE->_k == k)
{
break;
}
++pE;
}
if(pE == a_Entries + sz_EntryCount)
{
return NULL;
}
return pE;
}
};
HASH_FUNCTION& _hf;
KEY_COMP _kc;
size_t sz_TableSize;
double d_MultFactor; //Recalculate this as 1/sz_TableSize everytime sz_TableSize changes.
size_t sz_NextResizeLimit;
size_t sz_EntryCount;
double d_ExpectedLoadFactor;
double d_CurrentLoadFactor;
//If the load factor is relatively high (say >0.5 assuming sizeof(entry) == 2*sizeof(size_t)), it is more space efficient to keep a straight bucket array. But if the load factor is low, memory consumption would be lower if a pointer array of Entries is used here. But, because we would not be much concerned with a little additional memory being used when there are few entries, I think array of bucket objects is better. Further, it bypasses a pointer lookup. May have to reconsider is a situation where multiple hash tables are used (Perhaps as an array).
bucket* a_Buckets;
hash_container(const hash_container&);
hash_container& operator=(const hash_container&);
inline void calculateMultFactor() throw()
{
d_MultFactor = 1.0f/static_cast<double>(sz_TableSize + 1);
//sz_NextResizeLimit = static_cast<size_t>(d_ExpectedLoadFactor*sz_TableSize);
//Have a look at this.
//TODO
}
void resize(size_t szNewSize) throw(std::bad_alloc)
{
if(szNewSize == 0)
{
szNewSize = 1;
}
size_t szOldSize = sz_TableSize;
for(size_t szI = szNewSize; szI < szOldSize; ++szI)
{
a_Buckets[szI].~bucket();
}
a_Buckets = static_cast<bucket*>(realloc(a_Buckets, sizeof(bucket)*szNewSize));
if(a_Buckets == NULL)
{
throw std::bad_alloc();
}
//Unnecessary at the moment. But, just in case that bucket changes.
for(size_t szI = szOldSize; szI < szNewSize; ++szI)
{
new (&a_Buckets[szI]) bucket();
}
sz_TableSize = szNewSize;
calculateMultFactor();
}
inline bucket* get_bucket(const KEY& k) throw()
{
return a_Buckets + _hf(k, sz_TableSize);
}
inline bool need_resizing() const throw()
{
}
public:
//typedef iterator void*;
//typedef const_iterator void*;
//iterator Insert(KEY& k, VALUE& v);
//VALUE& Find(Key& k);
//const VALUE& Find(Key& k);
//iterator Find(KEY k);
//const_iterator Find(KEY k);
//void Delete(KEY& k);
//void Delete(iterator it);
//void Delete(const_iterator it);
class iterator
{
private:
entry* p_Entry;
bucket* p_Bucket;
friend class bucket;
public:
iterator(entry* pEntry)
:p_Entry(pEntry)
{
}
iterator()
{
p_Entry = NULL;
}
iterator(const iterator& it)
{
this->p_Entry = it.p_Entry;
}
inline VALUE& operator*() const
{
return p_Entry->_v;
}
inline bool operator==(const iterator& it) const
{
return this->p_Entry == it.p_Entry;
}
inline bool operator!=(const iterator& it) const
{
return !(*this == it);
}
inline iterator& operator=(const iterator& it)
{
this->p_Entry = it.p_Entry;
}
inline VALUE* operator->() const
{
return &p_Entry->_v;
}
inline iterator operator++()
{
return *this;
}
inline iterator& operator++(int)
{
//WRONG!!!
//TODO : Change this.
return *this;
}
};
private:
iterator _EndIt;
public:
hash_container(HASH_FUNCTION& hf, size_t szTableSize = 1024, double dLoadFactor = 0.7f, KEY_COMP kc = KEY_COMP())throw(std::bad_alloc)
:_hf(hf), sz_TableSize(szTableSize), d_ExpectedLoadFactor(dLoadFactor), _kc(kc)
{
if(d_ExpectedLoadFactor < 0.1f)
{
d_ExpectedLoadFactor = 0.1f;
}
a_Buckets = NULL;
sz_TableSize = 0;
if(szTableSize == 0)
{
szTableSize = 1;
}
resize(szTableSize);
d_CurrentLoadFactor = 0.0f;
sz_EntryCount = 0;
_EndIt = iterator(NULL);
}
virtual ~hash_container()
{
for(size_t szI = 0; szI < sz_TableSize; ++szI)
{
a_Buckets[szI].~bucket();
}
}
inline iterator find(const KEY& k) throw()
{
bucket* pBucket = get_bucket(k);
return pBucket->find(k);
}
inline bool insert(const KEY& k, const VALUE& v) throw(std::bad_alloc)
{
bucket* pBucket = get_bucket(k);
bool bRet = false;
try
{
bRet = pBucket->insert(k, v);
}
catch(std::bad_alloc& e)
{
//What now?
throw e;
}
if(bRet == true)
{
++sz_EntryCount;
}
return bRet;
}
inline VALUE& operator[](const KEY& k) throw(std::bad_alloc)
{
bucket* pBucket = get_bucket(k);
}
inline bool erase(const KEY& k) throw(std::bad_alloc)
{
bucket* pBucket = get_bucket(k);
bool bRet = false;
try
{
bRet = pBucket->erase(k);
}
catch(std::bad_alloc& e)
{
throw e;
}
if(bRet == true)
{
--sz_EntryCount;
}
return bRet;
}
inline iterator end() const
{
return _EndIt;
}
inline size_t size() const
{
return sz_EntryCount;
}
inline size_t table_size() const
{
return sz_TableSize;
}
inline double current_load_factor() const
{
return d_MultFactor*static_cast<double>(sz_EntryCount);
}
inline double expected_load_factor() const
{
return d_ExpectedLoadFactor;
}
};
}
#endif

.1. operator-> should almost always return a pointer type. When acting as an iterator with value_type T, it should return T*.
In some rarer cases, operator-> may return a different class type, which also has an operator-> member function.
.2. There are no technical requirements on what either form of operator++ must return, but the usual conventions make them act most like the built-in meanings.
class T {
public:
// pre-increment
T& operator++() { increment_me(); return *this; }
// post-increment
T operator++(int) { T copy(*this); increment_me(); return copy; }
//...
};
The built-in meaning of the pre-increment expression ++x first increments the number and then returns an lvalue to the incremented number. A return type of T& acts similarly.
The built-in meaning of the post-increment expression 'x++' increments the variable but returns an rvalue copy of the variable's previous value. So most user-defined overloads return a copy of the original value (which can practically never be a reference).

For an iterator type, how is operator-> overloaded?
It's not. The operator-> can only be overloaded on class types.
If you mean "How do I overload it to return an integer type".
Then the answer is you can't. The result of operator-> is itself de-referenced and as such must be a pointer type (or an object(reference) that is a class type with overload operator->()).
What value should it return assuming that it is an iterator for a collection of class T objects?
It will return a pointer to T
struct Y { int a; };
std::vector<Y> plop(/* DATA TO INIT*/);
std::vector<Y>::iterator b = plop.begin();
b->a = 5; // here b.operator->() returns a pointer to Y object.
// This is then used to access the element `a` of the Y object.
Why does operator++() return by class T& while operator++(int) return by class T?
Technically they can return anything. But usually they are implemented as you suggested.
This is because of the standard implementation of these methods:
class X
{
public:
// Simple one first. The pre-increment just increments the objects state.
// It returns a reference to itself to be used in the expression.
X& operator++()
{
/* Increment this object */
return *this;
}
// Post Increment: This has to increment the current object.
// But the value returned must have the value of the original object.
//
// The easy way to do this is to make a copy (that you return). The copy
// has the original value but now is distinct from this. You can now use
// pre-increment to increment this object and return the copy. Because
// the copy was created locally you can not return by reference.
X operator++(int)
{
X copy(*this);
++(*this);
return copy;
}
};
I understand these two represent prefix increment and postfix increment. But why the difference in return value?
See comments in above code.

operator-> should return a pointer to type T (ie. T*).
Postfix increment has to return a copy of the value, since it performs the increment but before the value has been used. Prefix increment can simply return *this after incrementing.
Simple implementations may look like this:
T T::operator++(int)
{
T temp = *this;
++*this;
return temp;
}
T& T::operator++()
{
this->value += 1;
return *this;
}

Related

How do move-only iterator implement postfix ++ operator?

What is the right way to implement an iterator that iterates over a Recordset provided below in C++ style?
class Recordset
{
public:
Recordset(const Recordset&) = delete;
Recordset& operator = (const Recordset&) = delete;
Recordset(Recordset&& other) noexcept = default;
Recordset& operator = (Recordset&&) = default;
//Moves to the next record. Returns false if the end is reached.
bool Next();
//Gets the current record as an instance of type T.
template <class T>
void Get(T& val);
};
my idea is that I probably do something like this:
template <class T>
class Iterator
{
public:
using iterator_category = std::forward_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = value_type*;
using reference = value_type&;
Iterator() = default;
Iterator(Recordset s) : m_i(std::move(s))
{
try_next();
}
Iterator(const Iterator&) = delete;
Iterator& operator = (const Iterator&) = delete;
Iterator(Iterator&& other) = default;
Iterator& operator = (Iterator&& other) = default;
T* operator-> () { return cur(); }
T* operator* () { return cur(); }
bool operator== (const Iterator& other) const noexcept
{
//They both are end().
return !m_v && !other.m_v;
}
bool operator!= (const Iterator& other) const noexcept
{
return !operator==(other);
}
Iterator& operator++ ()
{
this->try_next();
return *this;
}
Iterator operator++ (int)
{
Iterator tmp = *this; //would not compile.
this->try_next();
return tmp;
}
private:
bool try_next()
{
if (m_i.Next())
{
T val;
m_i.Get(val);
m_v = val;
return true;
}
return false;
}
T* cur()
{
T& val = *m_v;
return &val;
}
Recordset m_i;
std::optional<T> m_v;
};
template <class T>
std::ranges::subrange<Iterator<T>> make_range(Recordset& s)
{
return std::ranges::subrange(Iterator<T>(s), Iterator<T>{});
}
and use it as follows:
struct Record { int x; std::string y; };
int main()
{
Recordset s;
for (Record& r : make_range(s))
{
std::cout << r.x << r.y << std::endl;
}
return 0;
}
The frist question is how do I implement Iterator operator++ (int) if both Recordset and Iterator are move-only? (temp and this can't point to different records, because there is only one current record in the recordset). Does C++20 require it?
The second question is it a good idea to implement end() in this way? (end() is a simply an iterator containing an empty optional)
Single pass move-only input iterators (A c++20 std::input_iterator) are only required to be weakly incremental, where (void) ++i has the same effect as (void) i++. You can simply have void operator++(int) { ++*this; }. Older requirements for iterators (Cpp17InputIterator) requires iterators to be copyable, and require operator++ to return that copy.
And for your second question, you might want to use a sentinel type, something like:
template<typename T>
bool operator==(const Iterator<T>& it, std::default_sentinel_t) {
return !it.m_v;
}
// != can be rewritten from ==, so no need to write one
template <class T>
auto make_range(Recordset& s)
{
return std::ranges::subrange(Iterator<T>(s), std::default_sentinel);
}
And if you need to work with a algorithm that can't use separate sentinel types, use ranges::common_view. Your current solution also works, except you need to have this == &other || (!m_v && !other.m_v);.

Circular array (Queue) Iterator

I have an iterator class for a queue (implemented as a circular array). I attach the code below. The problem is with the ++ operator. Once it reaches the end of the array, it circles back to its beginning, so the iterator points to the 1st element. It works fine but I have no way of implementing then end() iterator using this method. The functions that return the begin() and end() iterators inside the queue class can be seen at the bottom. The end() iterator should point to the rear of the queue, but when the array is full and rear equals the size of the array is does not work, the ++ operator will circle back instead of allowing it to the return the true end(), which is the element pointed by the rear. Any suggestions regarding this issue?
class IteratorForwQueue : std::iterator<std::forward_iterator_tag, P*> {
public:
typedef IteratorForwQueue iter;
IteratorForwQueue(P* e, Queue* q) : elem(e), _queue(q) {}
IteratorForwQueue(const IteratorForwQueue& it, Queue* q) :
elem(it.elem), _queue(q) {}
iter& operator++() {
if(elem >= (_queue->_elems + (_queue->_size - 1)) &&
_queue->_rear != _queue->_size)
elem = &(_queue->_elems[0]); // circle back passed the array
else
++elem;
return *this;
}
P& operator*() { return *elem;}
P* operator->() { return elem; }
bool operator==(const iter& it) { return elem == it.elem; }
bool operator==(const P& e) { return e == *elem; }
bool operator!=(const iter& it) { return elem != it.elem; }
bool operator!=(const P& e) { return e != *elem; }
private:
P* elem;
Queue<P>* _queue;
}; // end of iterator class
// ....
IteratorForwQueue begin() { return IteratorForwQueue(_elems + _front, this); }
IteratorForwQueue end() { return IteratorForwQueue(_elems + _rear, this); }
How about this, based on the idea that end does not have to be a "past-the-end" iterator, it can be anything:
class IteratorForwQueue : std::iterator<std::forward_iterator_tag, P*> {
public:
typedef IteratorForwQueue iter;
IteratorForwQueue(P* e, size_t sz, size_t rear) : elem(e), _base(e), _sz(sz), _rear(rear) {}
IteratorForwQueue(const IteratorForwQueue& it, size_t sz) :
elem(it.elem), _base(it.elem), _sz(sz) {}
iter& operator++() {
if(elem - _base == _rear) {
elem = nullptr;
_base = 0;
_sz = 0;
_rear = 0;
return *this;
}
if(elem >= _base + (_sz - 1))
elem = _base; // circle back passed the array
else
++elem;
return *this;
}
P& operator*() { return *elem;}
P* operator->() { return elem; }
bool operator==(const iter& it) { return elem == it.elem; }
bool operator==(const P& e) { return e == *elem; }
bool operator!=(const iter& it) { return elem != it.elem; }
bool operator!=(const P& e) { return e != *elem; }
private:
P* elem;
P* _base; // can't access members of outer class
size_t _sz; // can't access members of outer class
size_t _rear;
}; // end of iterator class
// ....
IteratorForwQueue begin() { return IteratorForwQueue(_elems + _front, _size, _rear); }
IteratorForwQueue end() { return IteratorForwQueue(nullptr, 0, 0); }
const IteratorForwQueue cbegin() const { return IteratorForwQueue(_elems + _front, _size); }
const IteratorForwQueue cend() const { return IteratorForwQueue(_elems + _rear, _size); }
BTW, it would be better to have a pointer back to the original datastructure, make the iterator a friend class and fetch _base, _sz and _rear on demand.

Forward Iterator on a Stack

i have to implement a forward iterator on a stack based on arrays. I can't use std::vectors or anything, i just need that. My development of this program stopped when i begun with forward iterator, and in particular with the operators.
I have a method that takes a generic sequence, and from that, given an offset, creates a stack:
template <typename IterT>
stack(IterT begin, IterT end) : _stack(0), _size(0), _capacity(0) {
try {
for(; begin!=end; ++begin) {
push(static_cast<T>(*begin));
}
}
catch(...) {
clear(); //my method to destroy the stack
throw;
}
}
In my main i do the following:
int a[5] = {1, 2, 3, 4, 5};
stack<int> sint(a, a+5);
cout << sint << endl;
But when the code runs the stack is created but not printed. Can somebody help me? And also give me other helps(on code indentation, improvements, etc...) Thank you, I will post the iterator code forward.
class const_iterator {
const T* data;
unsigned int index;
public:
typedef std::forward_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
const_iterator() : data(0){}
const_iterator(const T* arr) : data(arr) {}
const_iterator(const const_iterator &other)
: data(other.data){ }
const_iterator& operator=(const const_iterator &other) {
data = other.data;
return *this;
}
~const_iterator() {
data = 0;
}
reference operator*() const {
return *data;
}
pointer operator->() const {
return &(data);
}
const_iterator operator++(int) {
const_iterator tmp(*this);
++*this;
return tmp;
}
const_iterator& operator++() {
++data;
return *this;
}
bool operator==(const const_iterator &other) const {
return data[index] == other.data[index];
}
bool operator!=(const const_iterator &other) const {
return data[index] != other.data[index] ;
}
private:
friend class stack;
const_iterator(unsigned int ind) :
index(ind){}
}; // class const_iterator
const_iterator begin() const {
cout << "begin" << _stack[_size-1] << endl;
return const_iterator(_stack[_size-1]);
}
const_iterator end() const {
cout << "end" << _stack[0] << endl;
return const_iterator(_stack[0]);
}
Last but not least i redefined the << operator to fit the iterator:
template <typename T>
std::ostream &operator<<(std::ostream &os, const stack<T> &st) {
typename stack<T>::const_iterator i, ie;
for(i = st.begin(), ie = st.end(); i!=ie; ++i){
os << *i << std::endl;
}
return os;
}
The code for the stack is the following (I omitted something for readability).
stack()
: _capacity(0), _size(0), _stack(0){}
void push (const T &value){
if (_size == _capacity){ //raddoppio la dimensione
if(_capacity == 0)
++_capacity;
_capacity *= 2;
T* tmp = new T[_capacity];
copy_n(_stack, _size, tmp);
swap(_stack, tmp);
delete[] tmp;
}
_stack[_size] = value;
++_size;
}
void pop(){
T _tmp;
if(!is_empty()){
_tmp = _stack[_size-1];
--_size;
}
}
If you want to create an iterator that looks like a pointer, you don't need index, because data plays its role. Comparison operator should compare datas, not values:
bool operator==(const const_iterator &other) const {
return data == other.data;
}
If you want to create a reverse iterator, it is slightly more complex. First, operator++ should decrement data. Second, dereference operator should return not *data, but *(data - 1). Third, data in the begin() iterator should point to stack[size], and data in the end() iterator should point to stack[0]. You don't need a destructor in any case.
I followed the previous advices and here's the edited result, still i can't figure out how to properly use the constructor in the private section
class const_iterator {
const T *data;
public:
/* ITERATOR TRAITS HERE */
const_iterator() : data(0){}
const_iterator(const T* arr) : data(arr) {}
const_iterator(const const_iterator &other)
: data(other.data){ }
const_iterator& operator=(const const_iterator &other) {
data = other.data;
return *this;
}
~const_iterator() {
data = 0;
}
reference operator*() const {
return *data;
}
pointer operator->() const {
return &(data);
}
const_iterator operator++(int) {
const_iterator tmp(*this);
++*this;
return tmp;
}
const_iterator& operator++() {
++data;
return *this;
}
bool operator==(const const_iterator &other) const {
return data == other.data;
}
bool operator!=(const const_iterator &other) const {
return data != other.data;
}
private:
friend class stack;
const_iterator(const T *d) {
data = d;
}
}; // classe const_iterator
const_iterator begin() const {
return const_iterator(_stack[_size-1]);
}
const_iterator end() const {
return const_iterator(_stack[0]);
}

How do I update this old C++ doubly linked list code to C++11?

I have the following code written in what would now be called C++98
It implements a doubly linked list, and I was considering updating it to C++11, but I had the following concerns:
it would seem that using std::unique_ptr makes sense, but there
still needs to be other pointers "sharing" the unique_ptr. I.e if
nxt is a unique_ptr, what do I do with prv and iterator::it?
it would seem that it should take an allocator as a template
paraemter, but it will be an allocator of type T, not LLNode?
are there any traits, that need to be "registered"?
if move semantics are used, what methods should be defined e.g.
operator=() && etc.?
what undefined behavior (if any) needs to be corrected?
what am i not concerned about that I should be?
An accepted answer would briefly discuss the concerns, and then reimplement in C++11 (admittedly, it's a lot of code, but most will be just cut and paste)
A good answer will briefly discuss the concerns, and then reimplement in C++11 the minimal amount to illustrate how to apply those concepts
using standard algorythms would make sense, using a standard container defeats the purpose
appropriate use of C++11 features is encouraged where it makes sense e.g. foreach as opposed to just using lambdas because it will work
This is not homework
The code:
template <class T>
class LinkedList
{
class LLNode
{
public:
LLNode() // For Sentinel (requires that T has a default constructor)
{} // set prv/nxt directly
explicit LLNode(const T& x) : data(x) // For normal nodes
{} // set prv/nxt directly
T& get_data() const
{
return const_cast<T&>(data);
}
LLNode * prv;
LLNode * nxt;
private:
T data;
};
public:
class iterator
{
public:
iterator(const LinkedList * p, LLNode * i)
: parent(const_cast<LinkedList *>(p)), it(i)
{}
iterator& operator ++() // pre
{
it = it->nxt;
return *this;
}
iterator operator ++(int) // post
{
iterator ret=*this;
it = it->nxt;
return ret;
}
iterator& operator --() // pre
{
it = it->prv;
return *this;
}
iterator operator --(int) //post
{
iterator ret=*this;
it = it->prv;
return ret;
}
T& operator *() const
{
return it->get_data();
}
T * operator ->() const
{
return &(it->get_data());
}
bool operator ==(const iterator& rhs) const
{
return it == rhs.it;
}
bool operator !=(const iterator& rhs) const
{
return it != rhs.it;
}
void erase()
{
parent->remove(it);
}
void insert_after(const T& x)
{
LLNode * add= new LLNode(x);
parent->insert(it, add);
}
void insert_before(const T& x)
{
LLNode * add= new LLNode(x);
parent->insert(it->prv, add);
}
private:
LinkedList * parent;
LLNode * it;
};
// Linked List class definition
LinkedList()
{
init();
}
LinkedList(const LinkedList& rhs)
{
init();
cp(rhs);
}
~LinkedList()
{
rm();
}
LinkedList operator =(const LinkedList& rhs)
{
if (this != &rhs)
{
rm();
cp(rhs);
}
return *this;
}
iterator begin() const
{
return iterator(this, sentinel.nxt);
}
iterator rbegin() const
{
return iterator(this, sentinel.prv);
}
iterator end() const
{
return iterator(this, const_cast<LLNode *>(&sentinel));
}
T& get_first() const // illegal if is_empty() is true
{
return sentinel.nxt->get_data();
}
T& get_last() const // illegal if is_empty() is true
{
return sentinel.prv->get_data();
}
size_t size() const
{
return count;
}
bool is_empty() const
{
return count==0;
}
void insert_first(const T& x)
{
LLNode * add= new LLNode(x);
insert(&sentinel, add);
}
void insert_last(const T& x)
{
LLNode * add= new LLNode(x);
insert(sentinel.prv, add);
}
void erase_first() // illegal if is_empty() is true
{
remove(sentinel.nxt);
}
void erase_last() // illegal if is_empty() is true
{
remove(sentinel.prv);
}
private:
void insert(LLNode * before, LLNode * added)
{
LLNode * after=before->nxt;
added->prv=before;
added->nxt=after;
before->nxt=added;
after->prv=added;
++count;
}
void remove(LLNode * node) // illegal if is_empty() is true
{
node->prv->nxt=node->nxt;
node->nxt->prv=node->prv;
delete node;
--count;
}
void cp(const LinkedList& rhs)
{
for (iterator i=rhs.begin(); i != rhs.end(); ++i)
{
insert_last(*i);
}
}
void rm()
{
LLNode * run=sentinel.nxt;
while (run != &sentinel)
{
LLNode * dead=run;
run=run->nxt;
delete dead;
}
}
void init()
{
count=0;
sentinel.nxt = sentinel.prv = &sentinel; // setup circular ref
}
LLNode sentinel;
size_t count;
};
EDIT - C++11 attempt based on Mooing Duck's answer :
template <class T, class ALLOC=std::allocator<T> >
class LinkedList
{
struct LLNode
{
LLNode * prv;
LLNode * nxt;
T& get_data() { return data; }
T data;
};
public:
class iterator
{
public:
using difference_type = ptrdiff_t;
using value_type = T;
using reference = T&;
using pointer = T*;
using iterator_category = std::bidirectional_iterator_tag;
iterator(LinkedList * p, LLNode * i) : parent(p), it(i)
{}
iterator& operator ++() // pre
{
it = it->nxt;
return *this;
}
iterator operator ++(int) // post
{
iterator ret=*this;
it = it->nxt;
return ret;
}
iterator& operator --() // pre
{
it = it->prv;
return *this;
}
iterator operator --(int) //post
{
iterator ret=*this;
it = it->prv;
return ret;
}
const T& operator *() const
{
return it->get_data();
}
T& operator *()
{
return it->get_data();
}
const T * operator ->() const
{
return &(it->get_data());
}
T * operator ->()
{
return &(it->get_data());
}
bool operator ==(const iterator& rhs) const
{
return it == rhs.it;
}
bool operator !=(const iterator& rhs) const
{
return it != rhs.it;
}
void erase()
{
parent->remove(it);
}
void insert_after(T& x)
{
auto add=parent->alloc_node(x);
parent->insert(it->nxt, add);
}
void insert_before(T& x)
{
auto add=parent->alloc_node(x);
parent->insert(it, add);
}
private:
LinkedList * parent;
LLNode * it;
};
class const_iterator
{
public:
using difference_type = ptrdiff_t;
using value_type = const T;
using reference = const T&;
using pointer = const T*;
using iterator_category = std::bidirectional_iterator_tag;
const_iterator(const LinkedList * p, const LLNode * i) : parent(p),
it(const_cast<LLNode *>(i))
{}
const_iterator(iterator& cvt) : parent(cvt.parent), it(cvt.it)
{}
const_iterator& operator ++() // pre
{
it = it->nxt;
return *this;
}
const_iterator operator ++(int) // post
{
const_iterator ret=*this;
it = it->nxt;
return ret;
}
const_iterator& operator --() // pre
{
it = it->prv;
return *this;
}
const_iterator operator --(int) //post
{
const_iterator ret=*this;
it = it->prv;
return ret;
}
const T& operator *() const
{
return it->get_data();
}
const T * operator ->() const
{
return &(it->get_data());
}
bool operator ==(const const_iterator& rhs) const
{
return it == rhs.it;
}
bool operator !=(const const_iterator& rhs) const
{
return it != rhs.it;
}
private:
const LinkedList * parent;
LLNode * it;
};
using my_alloc=typename
std::allocator_traits<ALLOC>::template rebind_alloc<LLNode>;
using my_traits=typename
std::allocator_traits<ALLOC>::template rebind_traits<LLNode>;
// Linked List class definition
LinkedList(const ALLOC& alloc = ALLOC() ) : mem(alloc)
{
init();
}
LinkedList(const LinkedList& rhs) : mem(rhs.mem)
{
init();
cp(rhs);
}
LinkedList(LinkedList&& rhs) : mem(rhs.mem) // Move
{
init();
shallow_cp(rhs);
}
~LinkedList()
{
rm();
}
LinkedList operator =(const LinkedList& rhs)
{
if (this != &rhs)
{
rm();
cp(rhs);
}
return *this;
}
LinkedList operator =(LinkedList&& rhs) // Move
{
if (this != &rhs)
{
rm();
shallow_cp(rhs);
}
return *this;
}
const_iterator begin() const
{
return const_iterator(this, sentinel.nxt);
}
iterator begin()
{
return iterator(this, sentinel.nxt);
}
const_iterator rbegin() const
{
return const_iterator(this, sentinel.prv);
}
iterator rbegin()
{
return iterator(this, sentinel.prv);
}
const_iterator end() const
{
return const_iterator(this, &sentinel);
}
iterator end()
{
return iterator(this, &sentinel);
}
T& front() // illegal if is_empty() is true
{
return sentinel.nxt->get_data();
}
T& back() // illegal if is_empty() is true
{
return sentinel.prv->get_data();
}
size_t size() const
{
return count;
}
bool is_empty() const
{
return count==0;
}
void insert_first(const T& x)
{
LLNode * add=alloc_node(x);
insert(&sentinel->nxt, add);
}
void insert_last(const T& x)
{
LLNode * add=alloc_node(x);
insert(&sentinel, add);
}
void erase_first() // illegal if is_empty() is true
{
remove(sentinel.nxt);
}
void erase_last() // illegal if is_empty() is true
{
remove(sentinel.prv);
}
private:
LLNode * alloc_node(const T& x)
{
auto ret = my_traits::allocate(mem,1);
my_traits::construct(mem, &(ret->data), x);
return ret;
}
void unalloc_node(LLNode * dead)
{
my_traits::deallocate(mem, dead, 1);
}
void insert(LLNode * after, LLNode * added)
{
LLNode * before=after->prv;
added->prv=before;
added->nxt=after;
before->nxt=added;
after->prv=added;
++count;
}
void remove(LLNode * node) // illegal if is_empty() is true
{
node->prv->nxt=node->nxt;
node->nxt->prv=node->prv;
unalloc_node(node);
--count;
}
void cp(const LinkedList& rhs)
{
mem = rhs.mem;
for (const_iterator i=rhs.begin(); i != rhs.end(); ++i)
{
insert_last(*i);
}
}
void shallow_cp(LinkedList& rhs)
{
if (rhs.count)
{
count=rhs.count;
sentinel=rhs.sentinel; // shallow copy
// fix the links to the old sentinel
sentinel.nxt.prv=&sentinel;
sentinel.prv.nxt=&sentinel;
rhs.init();
}
}
void rm()
{
LLNode * run=sentinel.nxt;
while (run != &sentinel)
{
LLNode * dead=run;
run=run->nxt;
unalloc_node(dead);
}
}
void init()
{
count=0;
sentinel.nxt = sentinel.prv = &sentinel; // setup circular ref
}
LLNode sentinel;
size_t count;
my_alloc mem;
};
Is anything missing/wrong?
it would seem that using std::unique_ptr makes sense, but there still needs to be other pointers "sharing" the unique_ptr. I.e if nxt is a unique_ptr, what do I do with prv and iterator::it?
Don't. (1) It makes various internal algorithms trickier to perform without accidentally deleting a node, and (2) unique_ptr stores the deleter, in your case that's either (A) a copy of the iterator, or (B) a pointer to the iterator. Either one is a waste of space. The container should store the allocator, and the container should handle deletions.
it would seem that it should take an allocator as a template paraemter, but it will be an allocator of type T, not LLNode?
Containers take an allocator of type T, though all of them use a rebound type internally. The standard is that allocators to containers take type T, and that way every std::vector<T, allocator<?>> matches every other. Also, outsiders should not be able to access LLNode. You'd probably store a given_allocator<LLNode> internally though. That's why we have rebinding.
are there any traits, that need to be "registered"?
For containers, no. There are interfaces to match, but those are relatively obvious.
Yes, your iterators are supposed to register traits, but that's easily done by merely adding five typedefs at the front.
typedef ptrdiff_t difference_type; //usually ptrdif_t
typedef T value_type; //usually T
typedef T& reference; //usually T&
typedef T* pointer; //usually T*
typedef std::bidirectional_iterator_tag iterator_category;
if move semantics are used, what methods should be defined e.g. operator=() && etc.?
Obviously the container should be move constructable and move assignable if it makes sense, which it does 99.99% of the time. Even std::array has move operators. Also decide which member functions should support move-only-T (insert range via iterators, but not insert range + count), and which member functions support any T (emplace one).
what undefined behavior (if any) needs to be corrected?
It would appear that your insert(iterator, data) inserts the data after the iterator, when the standard is to insert before the iterator. Your way makes it impossible to add data to the beginning, and makes it possible to add data after the after-the-end.
Your iterators have but do not need remove and insert functions. I wouldn't have mentioned it, but in this case they require the iterators to be twice as big as needed. I'd offer a tentative suggestion to remove them, but only tentative. That's a small penalty, and potentially a useful feature.
Your operator = deallocates everything and then reallocates. It might be handy to simply copy element by element skipping that when possible. Trickier to code though.
You're missing a constructor that constructs from a pair of iterators.
Your iterators allow mutating of data from const containers. THIS ONE IS BIG
get_first and get_last are normally called front and back.
what am i not concerned about that I should be?
The big one that everyone overlooks is exception safety. Your code is exception safe, but that appears to be because your code is amazingly simple, and you skipped all of the "hard" parts :P
For more information review these:
Writing your own STL Container
How to implement an STL-style iterator and avoid common pitfalls?
Edit for your C++11 followup:
missing:
template LLNode(U&& x) //or, one T&& and one const T&
LinkedList(std::initializer_list x)
T& LLNode::get_data() const //important, your code doesn't even compile
T* iterator::operator->() const //important
const_iterator cbegin() const
const_iterator cend() const
const T& front() const //important
const T& back() const //important
template void assign(iter, iter);
void assign(std::initializer_list);
void swap(const X&);
Incorrect:
insert_* should probably take a U&& and use std::forward<U>(x). Variadic templates would be better.
no iterator pair constructor.
memory leak in alloc_node, and you should be constructing a LLNode, you have uninitialized prv/nxt.
unalloc_node never destroys, only deallocates, leading to a memory leak
Missing but pretty unimportant
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
reverse_iterator rbegin(); //optional
const_reverse_iterator rbegin() const;
const_reverse_iterator crbegin() const;
reverse_iterator rend(); //optional
const_reverse_iterator rend() const;
const_reverse_iterator crend() const;
Optional:
not sure why my_alloc and my_traits and they're identical.
inherit privately from the allocator. It's a little wierd, but commonly saves you 8 bytes.

C++ compile-time constant detection

There're cases when a library source is available, and it has to support variable parameters in general, but in practice these parameters are commonly constants.
Then it may be possible to optimize things by special handling of constant parameters (eg. use static arrays instead of heap allocation), but for that its necessary to determine whether something is a constant first (or maybe define some macros, but its less convenient).
So here's a working implementation.
Update: also here: http://codepad.org/ngP7Kt1V
Is it really a valid C++ ?
Is there a way to get rid of these macros? (is_const() can't be a function because the function dependence won't work in array size expression; also it can't be a template because that won't accept a variable parameter either. )
Update: Here's an update with something more like intended usage.
The compiler won't generate any code for the if(N==0) branch if N is not 0.
Same way we can switch to completely different data structures if we want.
Sure its not perfect, but that's why I posted this question.
#include <stdio.h>
struct chkconst {
struct Temp { Temp( int x ) {} };
static char chk2( void* ) { return 0; }
static int chk2( Temp ) { return 0; }
};
#define is_const_0(X) (sizeof(chkconst::chk2(X))<sizeof(int))
#define is_const_0i(X) (sizeof(chkconst::chk2(X))>sizeof(char))
#define is_const(X) is_const_0( (X)^((X)&0x7FFFFFFF) )
#define const_bit(X1,bit) (is_const_0i((X1)&(1<<bit))<<bit)
#define const_nibl(X1,bit) const_bit(X1,bit) | const_bit(X1,(bit+1)) | const_bit(X1,(bit+2)) | const_bit(X1,(bit+3))
#define const_byte(X1,bit) const_nibl(X1,bit) | const_nibl(X1,(bit+4))
#define const_word(X1,bit) const_byte(X1,bit) | const_byte(X1,(bit+8))
#define const_uint(X1) const_word(X1,0) | const_word(X1,16)
#define const_switch_word( X1, X2 ) (is_const(X1) ? const_word(X1,0) : X2)
#define const_switch_uint( X1, X2 ) (is_const(X1) ? const_uint(X1) : X2)
const int X1 = 222;
const int X2 = printf( "" ) + 333;
char Y1[ const_switch_word(X1,256) ];
char Y2[ const_switch_word(X2,256) ];
template< int N >
void test( int N1 ) {
char _buf[N>0?N:1];
char* buf = _buf;
if( N==0 ) {
buf = new char[N1];
}
printf( "%08X %3i %3i\n", buf, N, N1 );
}
#define testwrap(N) test< const_switch_word(N,0) >( N )
int main( void ) {
printf( "%i %i %i\n", X1, is_const(X1), sizeof(Y1) );
printf( "%i %i %i\n", X2, is_const(X2), sizeof(Y2) );
testwrap( X1 );
testwrap( X2 );
}
If you're working with GCC, use __builtin_constant_p to tell you whether something is a compile time constant. The documentation includes examples like
static const int table[] = {
__builtin_constant_p (EXPRESSION) ? (EXPRESSION) : -1,
/* ... */
};
is_const should be more reliable. On gcc-4.4 for example, the following:
int k=0;
printf("%d\n",is_const(k),is_const(k>0));
prints:
0,1
GCC is quite ambitious folding constant expressions which are not integral constant expressions by the words of the standard. A potentially better definition of is_const could be:
#define is_const(B)\
(sizeof(chkconst::chk2(0+!!(B))) != sizeof(chkconst::chk2(0+!(B))))
Aside from that, your technique is awesome, because I can finally write a SUPER_ASSERT macro which is checked during compilation if the assertion expression if compile-time and during runtime otherwise:
#define SUPER_ASSERT(X) {BOOST_STATIC_ASSERT(const_switch_uint(X,1));assert(X);}
I'll look into that const_switch_xxx() thing later. I have no idea how to implement another way, the deconstruct/reconstruct trick is brilliant.
If you can pass in a template parameter then it is guaranteed to be a constexpr (the Standard's term for compile-time expressions). If it's not passed by template parameter, then it's not a constexpr. There is no way around this.
What would be much easier is to hand-roll a stack allocated variable length array class using alloca. This will guarantee stack allocation for arrays, regardless of whether or not they're static or not. In addition, you can get much of the same iteration functionality of a vector/boost::array.
#define MAKE_VLA(type, identifier, size) VLA< (type) > identifier ( alloca( (size) * sizeof ( type ) ), (size) );
template<typename T> class VLA {
int count;
T* memory;
VLA(const VLA& other);
public:
// Types
typedef T* pointer;
typedef T& reference;
typedef const T* const_pointer;
typedef const T& const_reference;
typedef T value_type;
typedef std::size_t size_type;
class iterator {
mutable T* ptr;
iterator(T* newptr)
: ptr(newptr) {}
public:
iterator(const iterator& ref)
: ptr(ref.ptr) {}
operator pointer() { return ptr; }
operator const pointer() const { return ptr; }
reference operator*() { return *ptr; }
const reference operator*() const { return *ptr; }
pointer operator->() { return ptr; }
const pointer operator->() const { return ptr; }
iterator& operator=(const iterator& other) const {
ptr = iterator.ptr;
}
bool operator==(const iterator& other) {
return ptr == other.ptr;
}
bool operator!=(const iterator& other) {
return ptr != other.ptr;
}
iterator& operator++() const {
ptr++;
return *this;
}
iterator operator++(int) const {
iterator retval(ptr);
ptr++;
return retval;
}
iterator& operator--() const {
ptr--;
return *this;
}
iterator operator--(int) const {
iterator retval(ptr);
ptr--;
return retval;
}
iterator operator+(int x) const {
return iterator(&ptr[x]);
}
iterator operator-(int x) const {
return iterator(&ptr[-x]);
}
};
typedef const iterator const_iterator;
class reverse_iterator {
mutable T* ptr;
reverse_iterator(T* newptr)
: ptr(newptr) {}
public:
reverse_iterator(const reverse_iterator& ref)
: ptr(ref.ptr) {}
operator pointer() { return ptr; }
operator const pointer() const { return ptr; }
reference operator*() { return *ptr; }
const reference operator*() const { return *ptr; }
pointer operator->() { return ptr; }
const pointer operator->() const { return ptr; }
reverse_iterator& operator=(const reverse_iterator& other) const {
ptr = reverse_iterator.ptr;
}
bool operator==(const reverse_iterator& other) {
return ptr == other.ptr;
}
bool operator!=(const reverse_iterator& other) {
return ptr != other.ptr;
}
reverse_iterator& operator++() const {
ptr--;
return *this;
}
reverse_iterator operator++(int) const {
reverse_iterator retval(ptr);
ptr--;
return retval;
}
reverse_iterator& operator--() const {
ptr++;
return *this;
}
reverse_iterator operator--(int) const {
reverse_iterator retval(ptr);
ptr++;
return retval;
}
reverse_iterator operator+(int x) const {
return reverse_iterator(&ptr[-x]);
}
reverse_iterator operator-(int x) const {
return reverse_iterator(&ptr[x]);
}
};
typedef const reverse_iterator const_reverse_iterator;
typedef unsigned int difference_type;
// Functions
~VLA() {
for(int i = 0; i < count; i++)
memory[i].~T();
}
VLA(void* stackmemory, int size)
: memory((T*)stackmemory), count(size) {
for(int i = 0; i < count; i++)
new (&memory[i]) T();
}
reference at(size_type pos) {
return (reference)memory[pos];
}
const_reference at(size_type pos) {
return (const reference)memory[pos];
}
reference back() {
return (reference)memory[count - 1];
}
const_reference back() const {
return (const reference)memory[count - 1];
}
iterator begin() {
return iterator(memory);
}
const_iterator begin() const {
return iterator(memory);
}
const_iterator cbegin() const {
return begin();
}
const_iterator cend() const {
return end();
}
const_reverse_iterator crbegin() const {
return rbegin();
}
const_reverse_iterator crend() const {
return rend();
}
pointer data() {
return memory;
}
const_pointer data() const {
return memory;
}
iterator end() {
return iterator(&memory[count]);
}
const_iterator end() const {
return iterator(&memory[count]);
}
reference front() {
return memory[0];
}
const_reference front() const {
return memory[0];
}
reverse_iterator rbegin() {
return reverse_iterator(&memory[count - 1]);
}
const_reverse_iterator rbegin() const {
return const_reverse_iterator(&memory[count - 1]);
}
reverse_iterator rend() {
return reverse_iterator(memory[-1]);
}
const_reverse_iterator rend() const {
return reverse_iterator(memory[-1]);
}
size_type size() {
return count;
}
reference operator[](int index) {
return memory[index];
}
const reference operator[](int index) const {
return memory[index];
}
};
Note that I haven't actually tested this code, but it would be MUCH easier to grab, use, and maintain than to maintain that monstrosity in your OP.