Constructors for mutable and const versions of an iterator class

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Constructors for mutable and const versions of an iterator class相关的知识,希望对你有一定的参考价值。

According to section 3.2 of the book "Generic Programming and STL", it is recommended to define both mutable and const versions of an iterator class. However, the constructors provided in the example code are still not perfect because some cases are not covered.

Example code:


template <class Node, class Pointer, class Reference> struct node_wrap_base : public iterator<forward_iterator_tag, Node, ptrdiff_t, Poitner, Reference> {
  typedef node_wrap_base<Node, Node*, Node&> iterator;
  typedef node_wrap_base<Node, const Node*, const Node&> const_iterator;
  
  node_wrap_base(Pointer p = 0) : ptr(p) {}
  node_wrap_base(const iterator& x) : ptr(x.ptr) {}
  
  // ...
};

template <class Node> struct node_wrap : public node_wrap_base<Node, Node*, Node&> {
  typedef node_wrap_base<Node, Node*, Node&> Base;
  
  node_wrap(Node* p = 0) : Base(p) {}
  node_wrap(const node_wrap<Node>& x) : Base(x) {}
  
  // ...
};

template <class Node> struct const_node_wrap : public node_wrap_base<Node, const Node*, const Node&> {
  typedef node_wrap_base<Node, const Node*, const Node&> Base;
  
  const_node_wrap(const Node* p = 0) : Base(p) {}
  const_node_wrap(const node_wrap<Node>& x) : Base(x) {}
  
  // ...
};

For details, the constructors for the base class node_wrap_base only support the following construction modes (Here, it should be noted that we call those pointers or references as const pointers or const references, if they are related to constant objects):

  • Constructor: node_wrap_base(Pointer p = 0) : ptr(p) {}

    • Construct a mutable node_wrap_base from a mutable pointer.
    • Construct a const node_wrap_base from a const pointer.

    The derivation of the above constructors is based on the fact that node_wrap_base has the same argument type Pointer as that of the argument p.

  • Copy constructor: node_wrap_base(const iterator& x) : ptr(x.ptr) {}

    • Construct a mutable node_wrap_base from a mutable iterator by copying, which is because the iterator is defined as

      typedef node_wrap_base<Node, Node*, Node&> iterator;

      which is mutable.

    • Construct a const node_wrap_base from a mutable iterator by copying.

It can be seen that there are still two cases not considered by the example code. They are

  • Construct a const node_wrap_base from a mutable pointer.
  • Construct a const node_wrap_base from a const iterator by copying.

For the first constructor in the above, we might define it as below:


ndoe_wrap_base<Node, const Node*, const Node&>(Node* p) : ptr(p) {}

However, C++ does not allow template constructor. Therefore, we can only define it as


node_wrap_base(Node* p) : ptr(p) {}

This is still not correct. Because when the template argument Pointer passed to node_wrap_base is Node*, which also satisfies the above constructor‘s formulation, which means we have two overloaded functions with the same function signature. This is not allowed by C++. The correct way to define base class constructors is:

  • node_wrap_base(const_poitner p = 0) : ptr(p)

    • Construct a mutable iterator from a const pointer. This is allowed!
    • Construct a const iterator from a const pointer.
  • node_wrap_base(mutable_pointer p) : ptr(p)

    • Construct a mutable iterator from a mutable pointer.
    • Construct a const iterator from a mutable pointer.

It should also be noted that because there is already a default constructor as the first one above, the above second constructor should not have default argument.

For the mutable version of the child class, the two constructors defined in the example code as below can cover all possible situations:

  • node_wrap(Node* p = 0) : Base(p) {}: Construct a mutable iterator from a mutable pointer.
  • node_wrap(const node_wrap<Node>& x) : Base(x) {}: Construct a mutable iterator from a mutable iterator.

It can be seen that the constructors for mutable version of the iterator class are complete, because mutable iterators can only be constructed from mutable pointer or copied from another mutable iterator.

For the const version of the iterator, the two constructors defined in the example code are:

  • const_node_wrap(const Node* p = 0) : Base(p) {}: construct a const iterator from a const pointer.
  • const_node_wrap(const node_wrap<Node>& x) : Base(x) {}: construct a const iterator from a mutable iterator by copying.

There are still two cases not considered by the example code:

  • Construct a const iterator from a mutable pointer.
  • Construct a const iterator from a const iterator by copying.

They can be defined as below:

  • const_node_wrap(Node* p = 0) : Base (p) {}
  • const_node_wrap(const const_node_wrap<Node>& i) : ptr(i.ptr) {}

In this way, all the constructors are correctly created for mutable and const versions of iterator classes. The complete code is listed as below.


// This example code demonstrates how to define constructors for mutable and
// const versions of an iterator class.

#include <iostream>
#include <ostream>
#include <iterator>
#include <algorithm>

using namespace std;

template <class Node> struct const_node_wrap;

// Integer linked list node.
struct int_node {  
  // Overload << operator
  // Note: the << operator has two arguments with the first one as the ostream
  // object, therefore it cannot be a member function of int_node.
  friend ostream& operator<<(ostream& os, const struct int_node & i);

  // Constructor
  int_node(int v = 0, int_node* p = NULL) : val(v), next(p) {}
  // Copy constructor
  int_node(const int_node& i) : val(i.val), next(i.next) {}
  
  // Assignment from an int_node struct. After the assignment, the new value
  // should be comparable to the original one.
  int_node& operator=(const int_node& i) {
    val = i.val;
    next = i.next;
    
    return *this;
  }

  // Only assign value.
  int_node& operator=(int i) {
    val = i;

    return *this;
  }

  // Equality operator.
  bool operator==(const int_node& i) {return val == i.val && next == i.next;}
  bool operator==(const int i) {return val == i;}
  bool operator!=(const int_node& i) {return val != i.val || next != i.next;}
  bool operator!=(const int i) {return val != i;}

  // Sum operator.
  int_node operator+(const int_node& i) {
    int_node sum_node;
    sum_node.val = this->val + i.val;
    return sum_node;
  }

  int_node& operator+=(const int_node& i) {
    this->val += i.val;
    return *this;
  }

  int val;
  int_node* next;
};

ostream& operator<<(ostream& os, const int_node & i) {
  return os << i.val;
}

template <class Node, class Pointer, class Reference> struct node_wrap_base : public iterator<forward_iterator_tag, Node, ptrdiff_t, Pointer, Reference> {
  typedef Node* mutable_pointer;
  typedef const Node* const_pointer;
  typedef Node& mutable_reference;
  typedef const Node& const_reference;
  typedef node_wrap_base<Node, mutable_pointer, mutable_reference> iterator;
  typedef node_wrap_base<Node, const_pointer, const_reference> const_iterator;

  Pointer ptr;

  //! Default constructor and constructor from a pointer, which can be mutable or
  //! const.
  //! If it is to construct a mutable iterator from a const pointer, which is
  //! invalid. The only situation is construct a const iterator from a const
  //! pointer.
  node_wrap_base(const_pointer p = 0) : ptr(p) { cout << "    *node_wrap_base construct from const pointer" << endl; }
  //! Default constructor and constructor from a mutable pointer.
  //! It includes two situations:
  //! - Construct a mutable iterator from a mutable pointer.
  //! - Construct a const iterator from a mutable pointer.
  //! mutable pointer.
  node_wrap_base(mutable_pointer p) : ptr(p) { cout << "    *node_wrap_base construct from mutable pointer" << endl; }

  //! Copy constructor
  //! Copy construct a const iterator from a const iterator, while the case for
  //! copy construct a mutable iterator from a const iterator is invalid.
  node_wrap_base(const const_iterator& i) : ptr(i.ptr) { cout << "    *node_wrap_base copy construct from const_iterator" << endl; }

  //! Copy construct
  //! Copy construct an iterator, either mutable or const, from a mutable
  //! iterator.
  //! It includes two situations:
  //! - Copy construct a mutable iterator from a mutable iterator.
  //! - Copy construct a const iterator from a mutable iterator.
  node_wrap_base(const iterator& i) : ptr(i.ptr) { cout << "    *node_wrap_base copy construct from mutable iterator" << endl; }

  //! Dereference operator
  Reference operator*() const { return *ptr; }
};

// Mutable wrapper class for the linked list node, which plays the role of iterator.
template <class Node> struct node_wrap : public node_wrap_base<Node, Node*, Node&> {
  typedef node_wrap_base<Node, Node*, Node&> Base;

  //! Constructor
  node_wrap(Node* p = 0) : Base(p) { cout << "    *node_wrap construct from mutable pointer" << endl; }
  //! Copy constructor
  node_wrap(const node_wrap<Node>& i) : Base(i) { cout << "    *node_wrap copy construct from mutable iterator" << endl; }
};

//! Const wrapper class for the linked list node, which plays the role of iterator.
template <class Node> struct const_node_wrap : public node_wrap_base<Node, const Node*, const Node&> {
  typedef node_wrap_base<Node, const Node*, const Node&> Base;

  //! Constructor
  const_node_wrap(Node* p = 0) : Base(p) { cout << "    *const_node_wrap construct from mutable pointer" << endl; }
  const_node_wrap(const Node* p) : Base(p) { cout << "    *const_node_wrap construct from const pointer" << endl; }

  //! Copy constructor
  const_node_wrap(const node_wrap<Node>& i) : Base(i) { cout << "    *const_node_wrap copy construct from mutable iterator" << endl; }
  const_node_wrap(const const_node_wrap<Node>& i) : Base(i) { cout << "    *const_node_wrap copy construct from const iterator" << endl; }
};

int main() {
  const int array_length = 10;
  int_node int_list[array_length];
  const int_node * int_list_const = int_list;

  cout << "Test constructors...\n";

  cout << ">>> Construct a mutable iterator from a mutable pointer." << endl;
  node_wrap<int_node> mutable_iter1(int_list);
  *mutable_iter1 = int_node(1);

  cout << ">>> Construct a const iterator from a mutable pointer." << endl;
  const_node_wrap<int_node> const_iter1(int_list);
  // *const_iter1 = int_node(5);   //! This should fail.

  cout << ">>> Construct a const iterator from a const pointer." << endl;
  const_node_wrap<int_node> const_iter2(int_list_const);
  // *const_iter2 = int_node(1); //! This should fail.

  //! cout << ">>> Construct a mutable iterator from a const pointer." << endl;
  //! node_wrap<int_node> const_iter3(int_list_const); //! This should fail.

  cout << "Test copy constructors...\n";

  cout << ">>> Copy construct a mutable iterater from a mutable iterator." << endl;
  node_wrap<int_node> mutable_iter2(mutable_iter1);
  *mutable_iter2 = int_node(1);

  cout << ">>> Copy construct a const iterator from a mutable iterator." << endl;
  const_node_wrap<int_node> const_iter4(mutable_iter1);
  // *const_iter4 = int_node(1); //! This should fail.

  cout << ">>> Copy construct a const iterator from a const iterator." << endl;
  const_node_wrap<int_node> const_iter5(const_iter1);
  // *const_iter5 = int_node(1);   //! This should fail.

  //! cout << ">>> Copy construct a mutable iterator from a cosnt iterator." << endl;
  //! node_wrap<int_node> mutable_iter3(const_iter1); //! This should fail.
  
  return 0;
}

以上是关于Constructors for mutable and const versions of an iterator class的主要内容,如果未能解决你的问题,请参考以下文章

mutable variable accessible from closure

Dart Constructors 构造函数使用技巧整理

Scala中关于auxiliary constructors

Qt creator -fno-elide-constructors 不工作

MapStruct - Ambiguous constructors found

Tip of the Week #74: Delegating and Inheriting Constructors