about
Bits Iter C++
06/16/2024
0
Bits Repo Code Bits Repo Docs

Bits: C++ Iteration

indexing into and iteration through std and user-defined collections

Synopsis:

This Bit demonstrates uses of C++ iterators to walk through enumerable collections. The purpose is to quickly acquire some familiarity with C++ iteration.
  • C++ Iterators are smart pointers that are provided by, and have special knowledge about the structure of, C++ iterable containers.
  • An iterable C++ container provides begin() and end() methods which return iterators pointing to the first and one past the last element of the container.
Demo Notes  
All of the languages covered in this demonstration support iteration using iterators. C++ iterators are provided by enumerable containers and have a public interface similar to, but smaller than native pointers. Any operation that moves data under an iterator, like insertion, causes invalidation and any subsequent dereference operation will throw an exception. User-defined types can also provide iterators, as shown in the example code below.
The examples below show how to use library and user defined types with emphasis on illustrating iteration through container elements.

1.0 Iterators

C++ iterators have the size and interface of native C++ pointers. Each standard iterable container, like std::vector<T> and std::unordered_map<K, V> provide iterators that are designed to step through the container's data structure. That is accomplished by defining operator overloads like C::operator++() where C is an iterable container.

Table 1. - Basic Iterator Operations

c.begin() returns iterator pointing to the first element of c ε C
c.end() returns iterator pointing to one past the last element of c ε C
auto iter = c.begin();
iter++;
val = *iter;
After executing this code iter points to the second element of c ε C.
val is the value of the second element of c.
for(auto item : c) {
   /* do something with item */
}
range-for extracts iterator from c and does the equivalent of:
for(auto itr = c.begin(); itr != c.end(); ++itr)
{ /* do some thing with *iter */ }
.

2.0 Source Code

Code for this "Iteration Bit" is partitioned into Basics, iteration over concretely specified containers, iteration over generic containers, and over custom iterable types.
Source code for this bit can be found here: Bits Iter C++ code
All of the bits code is here:
You can clone this repository and run all the examples locally.
Bits code

2.1 Basic Iteration

The code block below uses operations from Table 1. in executable code using std::vector<int> and int [N] collections. Note that native arrays don't have methods, so the std::library provides std::begin(arrayName) and std::end(arrayName) functions. Native pointers are used as iterators for native arrays. This block contains two panels seperated by a splitter-bar. You can expand the width of either panel by dragging the splitter bar or by clicking in either panel.
/*-----------------------------------------------
  Basic iterator operations
  - uses std::vector<int>
  - could be any other iterable container
*/
void iteratorBasics() {
  showOp("iterator basics");
  std::cout << std::endl;

  auto v = std::vector<int> { 1, 2, 3, 4, 5 };

  /* basic loop showing iterator usage */
  for(auto itr = v.begin(); itr != v.end(); ++itr) {
    std::cout << *itr << " ";
  }
  std::cout << " - using basic for loop" << std::endl;

  /* range-for uses iterator internally */
  for(auto item : v) {
    std::cout << item << " ";
  }
  std::cout << " - using range-for" << std::endl;
  std::cout << std::endl;

  int arr[5] { 1, 2, 3, 2, 1 };

  /* basic loop showing iterator usage */
  for(auto itr = std::begin(arr); itr != std::end(arr); ++itr) {
    std::cout << *itr << " ";
  }
  std::cout << " - using basic for loop with native array" << std::endl;

  /* range-for uses iterator internally */
  for(auto item : arr) {
    std::cout << item << " ";
  }
  std::cout << " - using range-for with native array" << std::endl;
  std::cout << std::endl;
}






  --- iterator basics ---




1 2 3 4 5  - using basic for loop





1 2 3 4 5  - using range-for








1 2 3 2 1  - using basic for loop with native array





1 2 3 2 1  - using range-for with native array

2.2 Iteration for Specified Collections

The demos in this code block use iterators explicitly or range-for loops which use iterators internally. All of the demos show different ways of displaying an iterable container's elements, using for loops, range-for loops, and do-while loops. All the demos uses either a std::vector<T> or a custom Point<T, N> type that provides the expected iterable operations:
begin(), end(), and index operators T& operator[](size_t i) and const T operator[](size_t i)
Implementation of Point<T, N> will be discussed in detail in section.
/*-----------------------------------------------
  demoIndexer(const std::vector<T>& v)
  - accepts std::vector<T>
  - creates comma separated list
  - uses indexing, not using iterator
*/
template<typename T>
void indexerVec(const std::vector<T>& v) {
  if(v.size() < 1)
    return;
  std::cout << "\n  " << v[0];
  for(size_t i = 1; i<v.size(); ++i) {
    std::cout << ", "  << v[i];
  }
}
void executeIndexerVec() {
  std::cout << "\nexecute demoIndexerVec(v)";
  auto v = std::vector<int> { 1, 2, 3, 2, 1 };
  indexerVec(v);
}
/*-----------------------------------------------
  demoIteratorVec
  - accepts std::vector<T> instances
  - uses iterator
*/
template<typename T>
void iteratorVec(const std::vector<T>& v) {
  auto itr = v.begin();
  std::cout << "\n  " << *itr;
  while(++itr != v.end()) {
    std::cout << ", " << *itr;
  }
}
void executeIteratorVec() {
  std::cout << "\nexecute demoIteratorVec(v)";
  auto v = std::vector<int> { 1, 2, 3, 2, 1 };
  iteratorVec(v);
}
/*-----------------------------------------------
  demoForLoopVec
  - accepts std::vector<T> instances
  - uses range-for to display elements
  - that uses iterator implicitly
*/
template<typename T>
void forLoopVec(const std::vector<T>& v) {
  std::cout << "\n  ";
  for(auto const &item : v) {
    std::cout << item << " ";
  }
}
void executeForLoopVec() {
  std::cout << "\nexecute demoForLoopVec(v)";
  auto v = std::vector<int> { 1, 2, 3, 2, 1 };
  forLoopVec(v);
}
/*-----------------------------------------------
  forLoopPoint
  - accepts Point<T, N> instances by constant reference.
  - uses range-for to display Point coordinates
  - creates comma separated list
*/
template<typename T, const size_t N>
void forLoopPoint(const Point<T, N>& p) {
  auto s = std::stringstream();
  s << "\n  ";
  for(auto const &item : p) {
    s << item << ", ";
  }
  auto str = s.str();
  /* remove last ", " */
  str.pop_back();
  str.pop_back();
  std::cout << str;
}
void executeForLoopPoint() {
  std::cout << "\nexecute demoForLoopPoint(v)";
  /* using initialization list */
  auto p = Point<int, 5> { 1, 2, 3, 2, 1 };
  forLoopPoint(p);
}
/*-----------------------------------------------
  whilerPoint
  - accepts Point<T, N> instances by constant reference.
  - explicit use of iterator to display PointN coordinates
  - creates comma separated list
*/
template<typename T, const size_t N>
void whilerPoint(const Point<T, N>& p) {
  auto itr = p.begin();
  std::cout << "\n  " << *itr++;
  while (itr < p.end()) {
    std::cout << ", " << *itr++;
  }
}
void executeWhilerPoint() {
  std::cout << "\nexecute demoWhilerPoint(v)";
  auto p = Point<int, 5>();
  /* using indexer */
  p[0] = 1;
  p[1] = 2;
  p[2] = 3;
  p[3] = 2;
  p[4] = 1;
  whilerPoint(p);
}

  --- collection specific iterations ---





execute indexerVec(v)
1, 2, 3, 2, 1

















execute iteratorVec(v)
1, 2, 3, 2, 1

















execute forLoopVec(v)
1 2 3 2 1
















execute forLoopPoint(v)
1, 2, 3, 2, 1























execute whilerPoint(v)
1, 2, 3, 2, 1

2.3 Iteration for Generic Collections

It only requires a little more effort to make functions that iterate over generic collections, e.g., collections that support:
begin(), end(), and index operators T& operator[](size_t i) and const T operator[](size_t i)
How to do that is what this section illustrates.
It is a bit harder to handle errors by returning an error message if a collection is not iterable instead of simply failing to compile with often obscure error messages. That takes a small amount of template metaprogramming. The last example does that.
/*-----------------------------------------------
  demoWhiler
  - is flexible function that accepts any
    iterable container
  - uses iterator on C.
  - creates comma separated list
*/
template<typename C>
void demoWhiler(const C& c) {
  auto itr = c.begin();  // uses const overload
  std::cout << "\n  " << *itr++;
  while (itr != c.end()) {
    std::cout << ", " << *itr++;
  }
}
void executeDemoWhiler() {
  std::cout << "\nexecute demoWhiler(c) with string";
  auto s = std::string("a string");
  demoWhiler(s);
  std::cout << "\nexecute demoWhiler(c) with vector";
  auto v = std::vector<double> { 1.0, 1.5, -1.5, -1.0, 0 };
  demoWhiler(v);
  std::cout << "\nexecute demoWhiler(c) with Point";
  auto p = Point<double, 5> { 1.0, 2.0, 3.0, 2.0, 1.0 };
  demoWhiler(p);
}
/*-----------------------------------------------
  whiler_guarded is flexible function that accepts
  any container
  - if C is iterable will use iterator on C.
  - If non-iterable input is detected,
    will display error msg and return.
  - decision is made at compile time.
  - is_iterable_v is defined in Analysis.h
  - max is the maximum number of items
    to show on one line
*/
template<typename C>
void whilerGuarded(
  const C& c, const std::string& name,
  size_t indent = 2, size_t max = 8
  ) {
  if constexpr(!is_iterable_v<C>) {  // decision at compile-time
    std::cout << "\n  whiler input type is not iterable";
    return;
  }
  else {
    std::cout << formatColl(c, name, "", indent, max);
    /*
      Analysis::formatColl uses range-based for loop to iterate
      over collection, folding output into rows of max items.

      Could have used the same iteration as in demoWhiler
      but wanted to show nicer formatting.
    */
  }
}
void executeWhilerGuarded() {
  std::cout << "\nexecute demoWhilerGuarded(c) with vector";
  auto v = std::vector<int> { 1, 2, 3, 2, 1, 0, -1, -2, -3, -4 };
  whilerGuarded(v, "vector");
  std::cout << "\nexecute demoWhiler(c) with double";
  whilerGuarded(3.5, "double");
  auto v2 = std::vector<int> { 1, 2, 3, 4, 5 };
}

  --- accepts any iterable collection ---














execute demoWhiler(c) with string
  a,  , s, t, r, i, n, g

execute demoWhiler(c) with vector
  1.0, 1.5, -1.5, -1.0, 0.0

execute demoWhiler(c) with Point
  1.0, 2.0, 3.0, 2.0, 1.0



  --- detects non-iterable input at compile-time ---




























execute demoWhilerGuarded(c) with vector
  vector : {
    1, 2, 3, 2, 1, 0, -1, -2,
    -3, -4
  }

execute demoWhiler(c) with double
  whiler input type is not iterable

2.4 Point<T, N> Definition

The Point<T, N> type was defined and discussed in the previous "Bits C++ Generics". Here, we extend that type by building in support for iteration. That is fairly simple: add declarations:
iterator, const_iterator, and value_type
and add methods:
iterator begin(), iterator end(),
const_iterator begin() const, and const_iterator end() const
These methods simply delegate delivery of the iterators to the underlying std::vector<T> coord methods.
/*-------------------------------------------------------------------
  Point<T, N> class represents a point in an N-Dimensional hyperspace.
  It uses a template parameter to support a variety of coordinate
  types, and uses a vector to hold any finite number of
  coordinates, specified by N.

  It also carries a Time t instance which conceptually is the time
  at which something was at that point in space. Time is a class
  defined for this demonstration in Time.h.

  All its special members, ctors, assignment, ... with the exception
  of constructor Point(), are declared default to indicate to a maintainer
  that compiler generated methods are correct and should not be provided.

  It does not provide an iterator nor begin() and end() members.
  Those will added in the iteration bit.
*/
template<typename T, const size_t N>
class Point {
public:
  using iterator = typename std::vector<T>::iterator;
  using const_iterator = typename std::vector<T>::const_iterator;
  using value_type = T;

  Point();                                      // default ctor
  Point(std::initializer_list<T> il);           // construct from list
  Point(const Point& pt) = default;             // copy ctor
  Point(Point&& pt) = default;                  // move ctor
  Point& operator=(const Point& pt) = default;  // copy assignment
  Point& operator=(Point&& pt) = default;       // move assignemnt
  ~Point() = default;                           // dtor

  void init(const std::vector<T>& v);

  std::string timeToString();
  void updateTime();
  Time& time();
  const size_t size() const;

  iterator begin();
  iterator end();
  const_iterator begin() const;
  const_iterator end() const;

  T& operator[](size_t index);                  // index oper
  const T operator[](size_t index) const;       // const index oper

  std::vector<T>& coords() { return coord; }    // accessor

  void show(const std::string& name);           // display contents
  size_t& left() { return _left; }              // display indent
  size_t& width() { return _width; }            // display width
private:
  std::vector<T> coord;
  Time tm;
  size_t _left = 2;   // default display indent
  size_t _width = 7;  // default display row width
};
/*-----------------------------------------------
  Point<T, N> constructor with size Template
  parameter
*/
template<typename T, size_t N>
Point<T, N>::Point()
  : tm(Time()) {
  for(size_t i=0; i<N; i++) {
    coord.push_back(T{0});
  }
}
/*-----------------------------------------------
  Fill coor with elements from initializer list li
  - if li is smaller than N then fill remainder with
    default values of T
  - if li is larger use first N elements of li
*/
template<typename T, size_t N>
Point<T, N>::Point(std::initializer_list<T> il)
  : tm(Time()) {
  size_t sz = std::min(N, il.size());
  size_t i = 0;
  for(auto item : il) {
    coord.push_back(item);
    if(++i == sz) {
      break;
    }
  }
  for(size_t i = il.size(); i<N; i++) {
    coord.push_back(T{0});
  }
}
/*---------------------------------------------
  Always returns N
*/
template<typename T, size_t N>
const size_t Point<T, N>::size() const {
  return coord.size();
}
/*---------------------------------------------
  index returns mutable value
*/
template<typename T, size_t N>
T& Point<T, N>::operator[](size_t index) {
  if (index < 0 || coord.size() <= index) {
    throw "Point<T, N> indexing error";
  }
  return coord[index];
}
/*---------------------------------------------
  index returns immutable value
*/
template<typename T, size_t N>
const T Point<T, N>::operator[](size_t index) const {
  if (index < 0 || coord.len() <= index) {
    throw "Point<T, N> indexing error";
  }
  return coord[index];
}

template<typename T, const size_t N>
typename Point<T, N>::iterator Point<T, N>::begin() {
  return coord.begin();
}
template<typename T, const size_t N>
typename Point<T, N>::iterator Point<T, N>::end() {
  return coord.end();
}
template<typename T, const size_t N>
typename Point<T, N>::const_iterator Point<T, N>::begin() const {
  return coord.begin();
}
template<typename T, const size_t N>
typename Point<T, N>::const_iterator Point<T, N>::end() const {
  return coord.end();
}

/*-----------------------------------------------
  Fill coor with elements from vector v
  - if v is smaller fill remainder with default
    values of T
  - if v is larger use first N elements of v
*/
template<typename T, size_t N>
void Point<T, N>::init(const std::vector<T>& v) {
  size_t sz = std::min(N, v.size());
  for(size_t i=0; i<sz; i++) {
    coord[i] = v[i];
  }
  for(size_t i = v.size(); i<N; i++) {
    coord[i] = T{0};
  }
}
/*---------------------------------------------
  returns string datetime
*/
template<typename T, size_t N>
std::string Point<T, N>::timeToString() {
  std::string ts = tm.toString();
  return ts;
}
/*---------------------------------------------
  set time to current time
*/
template<typename T, size_t N>
void Point<T, N>::updateTime() {
  tm = std::time(0);
}
/*---------------------------------------------
  returns current number of seconds in clock's
  epoch
*/
template<typename T, size_t N>
Time& Point<T, N>::time() {
  return tm;
}
/*-----------------------------------------------
  PointtN<T> display function
*/
template<typename T, size_t N>
void Point<T, N>::show(const std::string& name) {
  std::cout << "\n" << indent(_left) << name << ": " << "Point<T, N>";
  std::cout << " {\n";
  std::cout << fold(coord, _left + 2, _width);
  std::cout << indent(_left) << "}";
  std::cout << "\n" << indent(_left) << tm.toString() << std::endl;
}
/*-----------------------------------------------
  Overload operator<< required for
  showType(Point<T, N> t, const std::string& nm)
*/
template<typename T, size_t N>
std::ostream& operator<<(std::ostream& out, Point<T, N>& t2) {
  out << "\n" << indent(t2.left()) << "Point<T, N>";
  out << " {\n";
  out << fold(t2.coords(), t2.left() + 2, t2.width());
  out << indent(t2.left()) << "}";
  return out;
}

Point<T, N> Support for Iteration:


















⇐ Point Declaration


⇐ Iterator Declarations



















⇐ Iterator method declarations





















































































⇐ Iterator method definitions

                

2.5 Point<T, N> Demonstration

void demo_custom_type_Point_iteration() {
  using namespace Analysis;
  using namespace Points;

  showNote("iteration over user-defined Point<T, N>", 45, "\n");

  /*-- demonstrate Point<double 3> initialization lists --*/
  showOp("Point<double, 3> p1 {1.0, 1.5, 2.0, 1.5, 1.0 }");  // equal to N
  Point<double, 5> p1 {1.0, 1.5, 2.0, 1.5, 1.0};
  p1.left() = 0;
  p1.show("p1");
  std::cout << "\np1[1] = " << p1[1];           // indexing
  std::cout << "\np1.time().day() = "
            << p1.time().day();
  std::cout << "\np1.time().seconds() = "
            << p1.time().seconds() << "\n";

  showOp("iteration using basic loop", "\n");
  auto itr = p1.begin();
  while(true) {
    std::cout << *itr++ << " ";
    if(itr == p1.end()) {
      break;
    }
  }
  std::cout << std::endl;

  showOp("iteration using range-for", "\n");
  for(auto item : p1) {
    std::cout << item << " ";
  }
  std::cout << std::endl;

  showOp("Point<int, 10> p2 { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }");
  Point<int, 10> p2 { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  p2.left() = 0;
  p2.show("p2");
}
  ---------------------------------------------
    iteration over user-defined Point
  ---------------------------------------------

--- Point p1 {1.0, 1.5, 2.0, 1.5, 1.0 } ---        
p1: Point {
  1, 1.5, 2, 1.5, 1
}
Tue Jun 18 09:25:11 2024 local time zone

p1[1] = 1.5
p1.time().day() = 18
p1.time().seconds() = 11

--- iteration using basic loop ---
1.0 1.5 2.0 1.5 1.0

--- iteration using range-for ---
1.0 1.5 2.0 1.5 1.0 

--- Point p2 { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } ---   
p2: Point {
  1, 2, 3, 4, 5, 6, 7,
  8, 9, 10
}
Tue Jun 18 09:25:11 2024 local time zone

2.6 Analysis Functions

This code is very similar to that discussed in "Bits: Generic C++".
2.6 Analysis and Display Functions 
/*-------------------------------------------------------------------
  Display and Analysis function and globals definitions
---------------------------------------------------------------------
*/
const std::string nl = "\n";
/*-------------------------------------------------------------------
  Mutable globals are a common source of bugs.  We try not
  to use them, but will use DisplayParams here to control how
  the insertion operator sends instances to standard output.
*/
struct displayParams {
  size_t left = 2;    // number of spaces to indent
  size_t width = 7;   // width of display row
  size_t trunc = 40;  // replace text after trunc with ...
} DisplayParams;      // global object

/*-----------------------------------------------
  Overload operator<< required for
  showType(std::vector<T> v, const std::vector<T>& nm)
*/
template<typename T>
std::ostream& operator<<(std::ostream& out, std::vector<T>& v) {
  out << format(v, "vector<T>", "", DisplayParams.left, DisplayParams.width);
  return out;
}
/*-----------------------------------------------
  Display calling name, static class, and size
*/
template<typename T>
void showType(T t, const std::string &callname, const std::string& suffix) {
  std::cout << "\n  " << callname;          // show name at call site
  std::cout << " type: "
            << truncate(DisplayParams.trunc,typeid(t).name());  // show type
  std::cout << "\n  size:  " << sizeof(t);  // show size on stack
  std::cout << suffix;
}
/*-----------------------------------------------
  Display emphasized text
*/
inline void showNote(
  const std::string& txt,
  const size_t width,
  const std::string& suffix
) {
  auto fill = std::string(width, '-');
  print(fill);
  print("  " + txt);
  print(fill);
  std::cout << suffix;
}
/*-----------------------------------------------
  Display emphasized line
*/
inline void showOp(const std::string& opstr, const std::string& suffix) {
  std::cout << "\n--- " << opstr << " ---" << suffix;
}
/*-----------------------------------------------
  Helper function for formatting output
  - truncates line to N chars and adds ellipsis
*/
inline std::string truncate(size_t N, const char* pStr) {
  std::string temp(pStr);
  if(temp.length() > N) {
    temp.resize(N);
    return temp + "...";
  }
  return temp;
}
/*-----------------------------------------------
  Helper function for formatting output
  - generates string of n blanks to offset text
*/
inline std::string indent(size_t n) {
  return std::string(n, ' ');
}
/*-----------------------------------------------
  Helper function for formatting output
  - folds lines after width elements
*/
template<typename T>
std::string fold(std::vector<T>& v, size_t left, size_t width) {
  std::stringstream out("\n");
  out << indent(left);
  for(size_t i=0; i<v.size(); ++i) {
    if((i % width) == 0 && i != 0 && i != width - 1) {
      out << "\n" << indent(left);
    }
    if(i < v.size() - 1) {
      out << v[i] << ", ";
    }
    else {
      out << v[i] << "\n";
      break;
    }
  }
  return out.str();
}
/*-----------------------------------------------
  Helper function for formatColl
  - defines out << std::pair<K,V>
  - used in formatColl for associative containers
*/
template<typename K, typename V>
std::stringstream& operator<<(
  std::stringstream& out, const std::pair<K,V>& p
) {
  out << "{" << p.first << ", " << p.second << "}";
  return out;
}
/*-----------------------------------------------
  Format output for Collection types
  - any type with begin() and end() like
    all the STL containers.
  - if nm is larger than empty str displays nm : { + ...
  - if nm is empty str displays { + ...
  - if c.size() <= width displays on one line
  - if c.size() > width displays on folded stack of lines
*/
template<typename Coll>
std::string formatColl(
  const Coll& c, const std::string& nm = "", const std::string& suffix = "",
  size_t left = 2, size_t width = 7
) {
  std::string nameStr;
  std::string prologue;
  std::string epilogue;
  if(nm.size() == 0) {
    nameStr = "{ ";
  }
  else {
    nameStr = nm + " : { ";
  }
  if(c.size() <= width) {
    prologue = indent(left) + nameStr;
    epilogue = " }";
  }
  else {
    prologue = indent(left) + nameStr + "\n" + indent(left + 2);
    epilogue = "\n" + indent(left) + "}\n";
  }
  std::stringstream out;
  out << "\n" + prologue;
  size_t i = 0;
  for(const Coll::value_type& elem : c) {
    if((i % width) == 0 && i != 0 && i != width - 1) {
      out << "\n" << indent(left + 2);
    }
    if(i < c.size() - 1) {
      out << elem << ", ";
    }
    else {
      out << elem << epilogue << suffix;
      break;
    }
    ++i;
  }
  return out.str();
}
/*-----------------------------------------------
  Format output for scalar types like primitives
*/
template<typename T>
std::string formatScalar(
  const T& t, const std::string& nm, const std::string& suffix,
  size_t left
) {
  std::stringstream out;
  out << "\n" << indent(left) << nm << ": " << t << suffix;
  return out.str();
}
/*-----------------------------------------------
  Format output for strings
  - indent and embed in quotation marks
*/
template<typename T>
std::string formatString(
  const T& t, const std::string& nm, const std::string& suffix,
  size_t left
) {
  std::stringstream out;
  out << "\n" << indent(left) << nm << ": \"" << t << "\"" << suffix;
  return out.str();
}
/*-----------------------------------------------
  Defines is_iterable trait
  - detects STL containers and user-defined types
    that provide iteration
  - uses template metaprogramming, e.g., user code
    that runs at compile-time
https://stackoverflow.com/questions/13830158/check-if-a-variable-type-is-iterable
*/
template <typename T, typename = void>
struct is_iterable : std::false_type {};

// this gets used only when we can call
// std::begin() and std::end() on that type
template <typename T>
struct is_iterable<
  T,
  std::void_t
    <decltype(std::begin(std::declval<T>())),
    decltype(std::end(std::declval<T>()))>
> : std::true_type {};

template <typename T>
constexpr bool is_iterable_v = is_iterable<T>::value;

/*-----------------------------------------------
  Displays almost everything.
  - strings work better with formatString(...)
  https://www.cppstories.com/2018/03/ifconstexpr/
  Iteration is discussed in Bit Cpp_iter
*/
template<typename T>
std::string format(
  const T& t, const std::string& nm, const std::string& suffix,
  size_t left, size_t width
) {
  if constexpr(is_iterable_v<T>) {  // decision at compile-time
    return formatColl(t, nm, suffix, left, width);
  }
  else {
    return formatScalar(t, nm, suffix, left);
  }
}
/*-----------------------------------------------
  Display text after newline and indentation
*/
inline void print(const std::string& txt) {
  std::cout << "\n  " << txt;
}
/*-----------------------------------------------
  Display text after newline and indentation
  - provides trailing newline
*/
inline void println(const std::string& txt) {
  std::cout << "\n  " << txt << "\n";
}

Analysis and Display:






















 std::vector<T> operator<<







 showType










 showNote














 showOp






 truncate












 indent








 fold























 std::pair<K,V> operator<<
















 formatColl













































 formatScalar













 formatString


















 is_iterable






















 format














 print







 println
                  

2.7 Program Structure

The structure of this demonstration program is quite similar to that used in earlier bits, so is not shown by default.
Code Structure 
/*-------------------------------------------------------------------
  Bits_Iter.cpp
  - defines functions to iterate over collections
  - depends on Points.h to provide user-defined point class
  - depends on Analysis.h for several display and analysis functions
*/
#include <iostream>         // std::cout
#include <iomanip>          // std::fixed, std::setprecision
#include <sstream>          // std::stringstream
#include <memory>           // std::unique_ptr
#include <vector>           // vector<T> class
#include <array>            // array<T> class
#include <map>              // map<K,V> class
#include <set>              // set<T> class
#include <concepts>         // supports C++20 concepts
#include <algorithm>        // STL algorithms
#include "AnalysisIter.h"   // Analysis functions
#include "PointsIter.h"     // PointN<T> class declaration

using namespace Points;
/*-----------------------------------------------
  Note:
  Find all Bits code, including this in
  https://github.com/JimFawcett/Bits
  You can clone the repo from this link.
-----------------------------------------------*/
/*
  This demo uses the std::string and std::vector<T> classes
  and a user-defined class, Point<T, N>, to illustrate how
  types support indexing and iteration.
  - Each standard container, C, provides C::iterator,
    C::const_iterator, C::reverse_iterator, and
    begin() and end() methods that return iterators to
    the first and one past the last element of the
    collection, respectively.
*/
void iteratorBasics() {
  /* code elided */
}
template<typename T>
void indexerVec(const std::vector<T>& v) {
  /* code elided */
}
void executeIndexerVec() {
  /* code elided */
}
template<typename T>
void iteratorVec(const std::vector<T>& v) {
  /* code elided */
}
void executeIteratorVec() {
  /* code elided */
}
template<typename T>
void forLoopVec(const std::vector<T>& v) {
  /* code elided */
}
void executeForLoopVec() {
  /* code elided */
}
template<typename T, const size_t N>
void forLoopPoint(const Point<T, N>& p) {
  /* code elided */
}
void executeForLoopPoint() {
  /* code elided */
}
template<typename T, const size_t N>
void whilerPoint(const Point<T, N>& p) {
  /* code elided */
}
void executeWhilerPoint() {
  /* code elided */
}
template<typename C>
void demoWhiler(const C& c) {
  /* code elided */
}
void executeDemoWhiler() {
  /* code elided */
}
template<typename C>
void whilerGuarded(
  const C& c, const std::string& name,
  size_t indent = 2, size_t max = 8
  ) {
  /* code elided */
}
void executeWhilerGuarded() {
  /* code elided */
}
template<typename C, typename F>
void forEachOp(C& c, F f) {
  /* code elided */
}
void executeForEachOp() {
  /* code elided */
}
template<typename C>
inline void showCSL(const C& c, const std::string& nm, size_t max, size_t indent) {
  /* code elided */
}
void executeForEachAlgorithm() {
  /* code elided */
}
/*-------------------------------------------------------------------
  Demonstration starts here
*/
void testFormat();

int main() {

    showNote("Demonstrate C++ Iteration", 30, "\n");

    iteratorBasics();

    showOp("collection specific iterations", nl);
    std::cout << std::fixed;
    std::cout << std::setprecision(1);
    executeIndexerVec();
    executeIteratorVec();
    executeForLoopVec();
    executeForLoopPoint();
    executeWhilerPoint();
    print();

    showOp("accepts any iterable collection", nl);
    executeDemoWhiler();
    print();

    showOp("detects non-iterable input at compile-time", nl);
    executeWhilerGuarded();
    print();

    showOp("using lambda to operate on items", nl);
    executeForEachOp();
    std::cout << "\n";

    showOp("using std::for_each algorithm to modify items", nl);
    executeForEachAlgorithm();

    demo_custom_type_Point_iteration();

    // #define TEST
    #ifdef TEST
    testFormat();
    #endif

    print("\n  That's all Folks!\n\n");
}
void testFormat() {
  /* code elided */
}

Code Structure:

This program is partitioned into 3 header-only 
libraries and a demonstration file that uses
them.
The libraries are:
  - PointsIter.h
  - Time.h, included in Points.Iter.h
  - AnalysisIter.h
and the demonstration file is:
  - Bits_Iter.cpp
PointsIter.h:
  - defines Point<N, T>
  - includes Time.h
Time.h
  - defines Time and Timer types.
AnalysisIter.h:
  - defines several functions used to analyze
    type instances and display them.
Bits_IterCpp.cpp
  - includes the libraries
  - defines several demonstration functions,
    each of which demonstrates iteration
    techniques for types in std, std::library,
    and user-defined type Point<T, N>
Each library declares at the beginning: 
  #ifndef [libname]
  #define [libname]
and at the end:
  #endif
That ensures that each header is only compiled once,
avoiding multiple definitions and skipping unnec-
essary compiles.
                

3.0 Build

C:\github\JimFawcett\Bits\Cpp\Cpp_Iter
> cd build
C:\github\JimFawcett\Bits\Cpp\Cpp_Iter\build
> cmake ..
-- Selecting Windows SDK version 10.0.22000.0 to target Windows 10.0.22621.
-- Configuring done
-- Generating done
-- Build files have been written to: C:/github/JimFawcett/Bits/Cpp/Cpp_Iter/build
C:\github\JimFawcett\Bits\Cpp\Cpp_Iter\build
> cmake --build .
MSBuild version 17.5.1+f6fdcf537 for .NET Framework

  Checking Build System
  Building Custom Rule C:/github/JimFawcett/Bits/Cpp/Cpp_Iter/CMakeLists.txt
  Cpp_Iter.vcxproj -> C:\github\JimFawcett\Bits\Cpp\Cpp_Iter\build\Debug\Cpp_Iter.ex
  e
  Building Custom Rule C:/github/JimFawcett/Bits/Cpp/Cpp_Iter/CMakeLists.txt
C:\github\JimFawcett\Bits\Cpp\Cpp_Iter\build
>

4.0 VS Code View

The code for this demo is available in github.com/JimFawcett/Bits. If you click on the Code dropdown you can clone the repository of all code for these demos to your local drive. Then, it is easy to bring up any example, in any of the languages, in VS Code. Here, we do that for Cpp\Cpp_Iter. Figure 1. VS Code IDE - Debug Cpp_Iter

3.0 References

Reference Description
C++ Story: Standard Template Library Covers containers, iterators, and STL algorithms
C++ Story E-book with thirteen chapters covering most of intermediate C++
C++ Bites Relatively short feature discussions