about
Bits Content Prototype
05/10/2024
0
Bits Repo Code Bits Repo Docs

Bits: C++ Generics

generic functions and classes

Synopsis:

Generics are code generators that allow you to write one generic function or class that compiles into a separate concrete function or class for each unique list of specified type parameters.
This page demonstrates creation and use of C++ generic functions, types, and their objects. The purpose is to quickly acquire some familiarity with their use and implementations.
  • C++ Generics are defined with template functions, patterns for defining functions, and template classes, patterns for defining types. Each template has one or more abstract type parameters and a pattern for using them to define a class or function.
  • Template instantiation compiles an instance of a template with specific types defined by the application. The result is a function or class definition for each unique parameter type list.
Demo Notes  
Three of the languages: C++, Rust, and C# provide generics. Each generic function or class is a pattern for defining functions and classes of a specific type. Thus a generic class is a pattern for making patterns. The other two, Python and JavaScript, are dynamically typed and already support defining functions and classes for multiple types, e.g., no need for generics. This demonstration illustrates use of generic classes and objects, which for C++, Rust, and C#, are defined in a stackframe or in the heap or both. All data for Python and JavaScript are stored in managed heaps.
Code shown in this page demonstrates C++ generics with a few standard library types, creates and uses custom types, then examines several generic functions.

1.0 Templates and Concepts

Templates are generic patterns for functions and classes that use placeholder template types, like T below. The patterns become specific functions and structs when the placeholders are replaced with specific defined types.
Template functions use the syntax:
Examples in Section 2.6.1
template <typename T1, T2>
auto f<T1, T2> (t:T1, ...) -> T2 {
   /* implementing code using T1 and T2 */
}
The keyword auto declares the return type to be inferred by the compiler, T1 is a placeholder for the type of the first argument, and T2 is the type of the function return value.

Template classes are defined with the syntax:
Examples in Section 2.5
template<typename T, const size_t N>
class Point
{
   /* code declaring Point methods and
        data with N items of type T */
}
C++ template arguments may be types like T and constant integral literals like const size_t N. A Point class instance could be created like this:
Point<double, 5> p;

Concepts are declarations that may require any template type that implements the concept to implement semantic constraints defined by the std library, e.g.:
template <typename T>
concept Number = std::integral<T> || std::floating_point<T>;
Example in Section 2.4.1
template <typename T>
   requires Number<T>
class Stats { /* method implementations here */ }
Concepts may also require any template type that implements the concept to implement methods defined by the concept, e.g.:
Example in Section 2.4.1
template <typename T>
concept Show = requires (T t, const std::string& s) {
   t.show(s)
};

C++ Templates are compiled in two phases1:
  1. Compilation of template patterns with unknown parameter types:
    template code is checked for syntax errors. Unknow names that don't depend on template parameters are discovered, and static assertions that don't depend on template patterns are checked.
  2. Instantiation with specific parameter types in using code:
    When translating user code2, the compiler must see pattern definitions as well as specific types for parameters3. Using that, code is generted if there are no errors.

  1. C++ Templates, The Complete Guide, Second Edition, Vandevoorde, Josuttis, Gregor
  2. Each *.cpp file and its included *.h files is compiled separately from all other *.cpp filies, then resulting object code is linked together as the final stage of translation, if statically linked.
  3. That means that each included file must contain, directly, or indirectly, all of the template definitions on which it depends.
The first phase compilation of a template class may succeed but the second pass fails because on or more of the concrete parameter types do not have operations the template expects. Concepts are specifications on the types a template function or class expects, and helps the compiler provide meaningful error messages.

2.0 Source Code

Code demonstrations are partitioned into several parts each of which has both class or function definition and demonstrated uses. Source code is available here. Download the entire Bits repository here.

1.1 Program Structure

The "Program Structure" dropdown below illustrates how this program is structured using six files: AnalysisGen.h, HelloTemplates.h, Stats.h, PointsGen.h, Time.h, and several functions defined in Bits_Generics.cpp. The first five files are header-only libraries and the sixth defines execution flow for demonstration of C++ generic library and user-defined types. Each file defines one type and a demonstration function that illustrates how the file's defined class and functions work. The main program file, Bits_Generics.cpp, invokes the demonstration functions from each of the header libraries.
/*---------------------------------------------------------
  Cpp_Generics.cpp
  - demonstrates creating and using std::library generic 
    types: array, basic_string, vector, and map
  - demonstrates creating and using user-defined generic 
    types: HelloTemplates, Stats, and Point
  - depends on HelloTemplates.h to provide user-defined 
    HelloTemplates class
  - depends on Stats.h to provide user-defined Stats class
  - depends on PointsGen.h to provide user-defined point
    class
  - depends on Analysis.h for several display and analysis
    functions
  */
  /*-----------------------------------------------
  Note:
  Find all Bits code, including this in
  https://github.com/JimFawcett/Bits
  You can clone the repo from this link.
  -----------------------------------------------*/

  #include <iostream>       // std::cout
  #include <memory>         // std::unique_ptr
  #include <vector>         // vector<T> class
  #include <array>          // array<T> class
  #include <map>            // map<K,V> class
  #include <unordered_map>  // unordered_map<k,V> class
  #include <set>            // set<T> class
  #include <thread>         // this_thread
  #include "AnalysisGen.h"  // Analysis functions
  #include "HelloTemplates.h"        // Stats class declaration
  #include "Stats.h"
  #include "PointsGen.h"    // Point<T, N> class declaration

  using namespace Analysis;
  using namespace Points;


  /*-----------------------------------------------
  Overload operator<< for std::vector,
  required for demo_std_generic_types()
  and testformats()
  */
  template<typename T>
  std::ostream& operator<<(std::ostream& out, std::vector<T>& v) {
  out << format(v, "vector<T>", "", DisplayParams.left, DisplayParams.width);
  return out;
  }

  /*-----------------------------------------------
  demonstrate use of std generic types
  */
  void demo_std_generic_types() {
  /* code elided */
  }

  /*-----------------------------------------------
  demonstrate use of generic functions
  */
  void demo_generic_functions() {
  /* code elided */
  }

  int main() {

  showNote("Demonstrate C++ Generics", 30, nl);

  demo_std_generic_types();
  demo_custom_type_HelloTemplates();
  demo_custom_type_Stats();
  demo_custom_type_Point();
  demo_generic_functions();

  testtime();

  for(size_t i=0; i<4; ++i) {
  testtimer();
  std::this_thread::sleep_for(std::chrono::seconds(1));
  }

  print("\n  That's all Folks!\n\n");
  }

Program Structure:

Commentary content in this panel is placed vertically,
adjacent to relevant lines of code in left panel.
















 Code begins with a series of #include statements that
import declarations for the standard library and also 
for four header only libraries defined as part of this
demonstration.




 This demonstration of C++ templates is partitioned
into files: AnalysisGen.h, HelloTemplates.h, Stats.h,
Pointsgen.h, Time.h, and Cpp_Generics.cpp.

The first five files are "header only" libraries, and
the last includes them either directly or indirectly to 
create the demonstration program. This partitioning 
makes learning, testing, and maintenance significantly 
easier than putting everything into one large file.

C++ Modules

The #include declarations, shown in the left panel, are 
being superceded by module imports, as of C++20. For 
example, all the standard library #includes, like 
#include <iostream>, are being replaced with a single 
"import std".

Individual libraries can be imported with declarations
like import <iostream>.

These modern module declarations are not used in this
demonstration because the build tool I use, CMake, does
not, as of version 3.9.2, process compiler generated
modules, e.g., the std library module.

Modules do work with code compiled in the Visual Studio
IDE. You will find a demonstration of that in the 
Bits Repository.





 Execution Flow:
Processing begins on entry to the main function which, 
in turn, invokes several functions, devoted to
demonstrating generic standard library types and three
generic custom types and generic functions.





 Two more functions are provided to demonstrate the
operation of Time and Timer class instances, used by
the Point<T, N> class.

This partitioning makes code easier to understand and
maintain.



1.2 Demo Standard Library Generic Types

Several of the often encountered generic standard collection types, std::array<T, N>, std::vector<T>, and std::map<Key, Value>, are illustrated here. we show their initialization and display using generic functions. The generic analysis and display functions are covered in Section 1.6 and Section 2.0. Here you see a side-by-side view of executable code with the output it provides. That proximity makes it easier to understand how the code works. The two panels below are separated by a "splitter bar". You can drag the bar horizontally to view hidden code or output. You can also click in either panel to widen and display overflow
/*-------------------------------------------------------------------*/
void demo_std_generic_types() {

  showNote("Demo std generic types", nl);
  showOp("array<int,4>",nl);
  auto a = std::array<int, 4> { 1, 2, 3, 4 };
  showArray(a);     // works only for arrays
  showSeqColl(a);   // works for any sequential collection

  showOp("vector<double>", nl);
  std::vector<double> v = { 1.0, 1.5, 2.0, 2.5 };
  std::cout << v << "\n"; // uses operator<< overload for vectors, above
  showSeqColl(v);         // any sequential collection
  std::cout << formatColl(v, "v", "\n"); // any STL collection

  showOp("std::map<string,int>", nl);
  std::map<std::string, int> m {
    {"zero", 0}, {"one", 1}, {"two", 2}, {"three", 3}
  };
  std::cout << formatColl(m, "m", "\n"); // any STL collection
  showAssocColl(m);  // coll elements must be std::pair<K,V>

  showOp("std::unordered_map<string,int>", nl);
  std::unordered_map<std::string, int> um {
    {"zero", 0}, {"one", 1}, {"two", 2}, {"three", 3}
  };
  std::cout << formatColl(um, "um", "\n"); // any STL collection
  showAssocColl(um);  // coll elements must be std::pair<K,V>
}




--------------------------------------------------
  Demo std generic types
--------------------------------------------------

--- array<int,4> ---
array<T,N> [1, 2, 3, 4]
Collection<T> [1, 2, 3, 4]

--- vector<double> ---
vector<T>: {
  1, 1.5, 2, 2.5
}
Collection<T> [1, 1.5, 2, 2.5]
v: {
  1, 1.5, 2, 2.5
}

--- std::map<string,int> ---
m: {
  {one, 1}, {three, 3}, {two, 2}, {zero, 0}
}
Collection<K,V> {
  {one, 1}, {three, 3}, {two, 2}, {zero, 0}
}

--- std::unordered_map<string,int> ---
um: {
  {three, 3}, {zero, 0}, {one, 1}, {two, 2}
}
Collection<K,V> {
  {three, 3}, {zero, 0}, {one, 1}, {two, 2}
}
                  

1.3 User-defined Class: HelloTemplates<T>

C++ generics are declared with a template<T1, T2, ...> prefix followed by a class or function declaration that depends on the unspecified types T1, T2, ...

1.3.1 HelloTemplates<T> Definition

This section declares a generic class to illustrate syntax required for that.
/*-------------------------------------------------------------------
  HelloTemplates.h defines HelloTemplates<T> class
  - HelloTemplates<T> is a simple demonstration of a template
    type that does'nt do anything very useful except to explain
    syntax.
*/
/*-------------------------------------------------------------------
  simple user-defined type to demonstrate template syntax
*/
#include "AnalysisGen.h"
using namespace Analysis;

template<typename T>
class HelloTemplates {
public:
  HelloTemplates() = default;
  HelloTemplates(T& tin) : t(tin) {};
  HelloTemplates(const HelloTemplates<T>& t) = default;
  HelloTemplates<T>& operator=(const HelloTemplates<T>&t) = default;
  ~HelloTemplates() = default;
  T& value() { return t; }
  void show();
private:
  T t;
};

template<typename T>
void HelloTemplates<T>::show() {
  std::cout << "  HelloTemplates<T> {\n  ";
  std::cout << "  type T: "
            << truncate(DisplayParams.trunc,typeid(t).name());  // show type
  std::cout << ", size: " << sizeof(t);  // show size on stack
  std::cout << ", value: " << t;
  std::cout << "\n  }\n";
}
/* template method specialization */
template<>
void HelloTemplates<std::vector<int>>::show() {
  std::cout << "  HelloTemplates<T> {\n  ";
  std::cout << "  type T: "
            << truncate(DisplayParams.trunc,typeid(t).name());  // show type
  std::cout << ", size: " << sizeof(t) << "\n    value: [ ";
  for (auto item : t) {
    std::cout << item << " ";
  };
  std::cout << "]\n  }\n";
}
/* partial template specialization of HelloTemplates class */
template<template<typename> typename V, typename T>
class HelloTemplates<V<T>> {
  HelloTemplates<V<T>>& operator=(const HelloTemplates<V<T>>& v) = default;
  ~HelloTemplates() = default;
  V<T>& value() { return val; }
  void show() {
    std::cout << "  HelloTemplates<V<T>> {\n  ";
    std::cout << "  type V<T>: "
              << truncate(DisplayParams.trunc,typeid(val).name());  // show type
    std::cout << ", size: " << sizeof(val) << "\n    value: [ ";
    for (auto item : val) {
      std::cout << item << " ";
    };
    std::cout << "]\n  }\n";
  }
private:
  V<T> val;
};

Purpose:
  
HelloTemplates<T> is a simple class, intended to 
illustrate template syntax.
  

 Basic Syntax:
  
Each generic class and function must be preceeded
by a template declaration:

  template<typename T, ...>

that has one or more typename arguments. T is an 
abstract type that will be replaced by a specific 
type in the using code.
  
So a template is a pattern for defining one unique
class or function for each unique type list used
by an application program.
  
HelloTemplates<int> and HelloTemplates<double> 
generate two unique classes.
  
HelloTemplates<T> simply holds, provides mutable access
to, and displays a single instance of the type T.  
That type can be almost anything that can send its 
value to std::cout.
  
Its special methods are all declared default and no 
definitions are provided. That means that the compiler 
will generate them by calling the operation applied to 
a HelloTemplates<T> instance on its composed instance 
of T.
  
 Specialization:
  
Template classes can have a specialized method for a 
specified type. Specialization is used when a method 
needs to treat the specified type differently than 
for all of the other generic types.
  
HelloTemplates<T>::show() has been specialized for
std::vector<int> to support displaying the vector's 
items.
  
The C++ language guarantees that when the template type 
T is a specialized type it will call the specialized 
method. If not a specialized type the generic method is 
called.
  

1.3.2 HelloTemplates<T> Demo

Source code example using HelloTemplates<T> is shown here along with output generated when it's executed.
/*-- demonstrate creation and use of HelloTemplates<T> --*/
void demo_custom_type_HelloTemplates() {

  println();
  showNote("Demo user-defined HelloTemplates<T>", 40);

  showOp("HelloTemplates<T>", nl);
  int arg = 42;
  HelloTemplates<int> demi(arg);
  demi.show();
  std::cout << std::endl;;

  double pi = 3.1415927;
  HelloTemplates<double> demd(pi);
  demd.show();

  /*-------------------------------------------------------
    specialization defined in HelloTemplates<T> class header
    HelloTemplates.h and used here
  */
  auto vs = std::vector<int> { 1, 2, 3 };
  HelloTemplates<std::vector<int>> demv(vs);
  demv.show();
}
----------------------------------------
  Demo user-defined HelloTemplates<T>
----------------------------------------

  --- HelloTemplates<T> ---

  HelloTemplates<T> {
    type T: int, size: 4, value: 42
  }

  HelloTemplates<T> {
    type T: double, size: 8, value: 3.14159
  }

  HelloTemplates<T> {
    type T: class std::vector<int,class std::allocat..., size: 32
    value: [ 1 2 3 ]
  }


                

2.4 User-defined Class: Stats<T>

This section defines a generic class that is intended to compute properties of a collection of numbers, e.g., max, min, sum, and avg. We explore what happens when the generic parameter type has some, but not all of the expected numerical operations.

2.4.1 Stats<T> Definition

/*-------------------------------------------------------------------
  Stats<T>
  - Stats<T> holds a std::vector<T> and provides methods for
    computing max, min, average of this collection
    of unspecified type T
  - Code builds as a template definition
  - Will fail to build instantiation if T is not a numeric type
*/
#include <iostream>
#include <vector>
#include <exception>
#include <concepts>
#include "AnalysisGen.h"
using namespace Analysis;

/*-------------------------------------------------------------------
  Stats<T> class provides several simple computational services on
  a vector of items who's type provides required arithmetic operations.
  - This class inhibits compiler generation of default constructor
    and assignment operator.
*/

template <typename T>
  concept Number = std::integral<T> || std::floating_point<T>;

template <typename T>
   requires Number<T>
class Stats {
public:
    Stats() = default;
    Stats(const std::vector<T>& v);
    Stats(const Stats<T>& s) = default;
    Stats<T>& operator=(Stats<T>& s) = default;
    size_t size();
    T max();
    T min();
    T sum();
    double avg();
    void show(const std::string& name="");
private:
    bool check();
    const std::vector<T>& items;
};
/*-------------------------------------------------------------------
  Constructor initialized with vector of values
*/
template<typename T>
   requires Number<T>
Stats<T>::Stats(const std::vector<T>& v) : items(v) {}

/*-------------------------------------------------------------------
  check that Stats instance contains at least one value
*/
template<typename T>
   requires Number<T>
bool Stats<T>::check() {
    return items.size() > 0;
}
/*-------------------------------------------------------------------
  returns number of data items
*/
template<typename T>
   requires Number<T>
size_t Stats<T>::size() {
    if(!check()) {
        throw "Stats is empty";
    }
    return items.size();
}
/*-------------------------------------------------------------------
  returns largest value (not necessarily largerst magnitude)
*/
template<typename T>
   requires Number<T>
T Stats<T>::max() {
    if(!check()) {
        throw "Stats is empty";
    }
    auto max = items[0];
    for( auto item : items) {
        if (item > max) {
            max = item;
        }
    }
    return max;
}
/*-------------------------------------------------------------------
  returns smallest value (not necessarily smallest magnitude)
*/
template<typename T>
   requires Number<T>
T Stats<T>::min() {
    if(!check()) {
        throw "Stats is empty";
    }
    auto min = items[0];
    for( auto item : items) {
        if (item < min) {
            min = item;
        }
    }
    return min;
}
/*-------------------------------------------------------------------
  returns sum of data values
*/
template<typename T>
   requires Number<T>
T Stats<T>::sum() {
    if(!check()) {
        throw "Stats is empty";
    }
    auto sum = T{0};
    for( auto item : items) {
        sum += item;
    }
    return sum;
}
/*-------------------------------------------------------------------
  returns average of data values
*/
template<typename T>
   requires Number<T>
double Stats<T>::avg() {
    if(!check()) {
        throw "Stats is empty";
    }
    auto sum = T{0};
    for( auto item : items) {
        sum += item;
    }
    return double(sum)/double(items.size());
}
/*-------------------------------------------------------------------
  displays current contents
*/
template<typename T>
   requires Number<T>
void Stats<T>::show(const std::string& name) {
    if(!check()) {
        throw "Stats is empty";
    }
    std::cout << "\n  " << name << " {\n    ";
    auto iter = items.begin();
    std::cout << *iter++;
    while(iter != items.end()) {
        std::cout << ", " << *iter++;
        std::cout.flush();
    }
    std::cout << "\n  }\n";
}
                  

Stats<T>:
 
This demonstration illustrates the use of C++ Concepts
introduced in C++20.

















 Concept definition


 The Stats<T> class contains a std::vector<T>
of items, and defines methods to carry out numerical 
operations on those, e.g., sum(), avg(), ...

The class, and each of its methods requires the
template parameter T to satisfy the Number concept,
e.g., it is either an integral or floating point type.

All templates are compiled in two phases.  The first 
checks template syntax but does not generate code as 
the size of items is unknown. The second phase occurs 
when using code supplies a specific type for T.  That 
will succeed if and only if all of the member function
invocations on methods of T are valid for that specific 
type.
 
So the second compilation phase will succeed for 
Stats<T> if and only if T is a numeric type that can 
be ordered, added, and divided, as those operations
are used in the class's method definitions.
 
During the second phase of compilation any methods 
that are not used by the program will not be compiled, 
so no code is generated for them.
 

2.4.2 Stats<T> Demonstration

Source code is available here. Download the entire Bits repository here.
/*-- demonstrate custom type Stats<T> --*/

void demo_custom_type_Stats() {

  println();
  showNote("Demo user-defined Stats<T>", 35);

  showOp("Stats<double> s(v)", nl);
  std::vector<double> v { 1.0, 2.5, -3.0, 4.5 };
  showSeqColl(v);
  Stats<double> s(v);
  std::cout << "  min: " << s.min();
  std::cout << ", max: " << s.max();
  std::cout << ", sum: " << s.sum();
  std::cout << ", avg: " << s.avg() << std::endl;

  showOp("Stats<double> s2 = s", nl);
  Stats<double> s2 = s;  // copy construction
  std::cout << "  min: " << s2.min();
  std::cout << ", max: " << s2.max();
  std::cout << ", sum: " << s2.sum();
  std::cout << ", avg: " << s2.avg() << std::endl;

  showOp("Stats<int> s3(u)", nl);
  std::vector<int> u { 1, 2, 3, 1 };
  showSeqColl(u);
  Stats<int> s3(u);
  std::cout << "  min: " << s3.min();
  std::cout << ", max: " << s3.max();
  std::cout << ", sum: " << s3.sum();
  std::cout << ", avg: " << s3.avg() << std::endl;

  /*--------------------------------------------------
  This works without the Number concept, with the
  exception of average. With concept the stats
  library fails to compile because strings are
  not ints or floats.
  */
  // showOp("Stats<std::string> ss", nl);
  // std::vector<std::string> vstr { "ab", "cd", "ef" };
  // Stats<std::string> ss(vstr);
  // std::cout << "  min: " << ss.min();
  // std::cout << ", max: " << ss.max();
  // std::cout << ", sum: " << ss.sum();
  //--------------------------------------------------
  // first compile phase:
  //   Stats<T>::avg() passess
  // second compile phase:
  //   Stats<std::string>::avg() fails to compile.
  //   No way to divide sum string by size integer in
  //   std::cout << ", avg: " << ss.avg() << std::endl;
  //   All the other methods compile successfully.

  println();
}

-----------------------------------
  Demo user-defined Stats<T>
-----------------------------------

  --- Stats<double> s(v) ---

  Collection<T> [1, 2.5, -3, 4.5]
  min: -3, max: 4.5, sum: 5, avg: 1.25

  --- Stats<double> s2 = s ---

  min: -3, max: 4.5, sum: 5, avg: 1.25

  --- Stats<int> s3(u) ---

  Collection<T> [1, 2, 3, 1]
  min: 1, max: 3, sum: 7, avg: 1.75

2.5 User-defined Class: Point<T, N>

Point<T, N> is a parameterized class representing points in an N-dimensional hyperspace with a time member indicating the date and time at which something was at that point.

2.5.1 Point<T, N> Definition

The PointsGen.h file defines namespace Points and class Point. Point uses an instance of class Time defined in Time.h to provide a time-stamp for each point.
/*-------------------------------------------------------------------
  PointsGen.h defines point classe Point<T, N>
  - Point<T, N> represents points with N coordinates of
    unspecified type T and a Time t.
*/
#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
#include <concepts>
#include "AnalysisGen.h"
#include "Time.h"

namespace Points {

  //using namespace Analysis;

  /*-------------------------------------------------------------------
    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:
    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;
    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];
  }
  /*-----------------------------------------------
    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;
  }
}
Concept:

A Point<T, N> instance could represent the state of 
some weather event at a given time, e.g., latitude, 
longitude, series of altitudes, wind velocities, 
dewpoint, ...

A sequence of instances could represent evolution of a 
complex chemical process, e.g. temperature, pressure, 
sequence of concentrations of molecules, ...

Purpose:

This example illustrates use of both type T and 
numeric N template parameters. N is the fixed number 
of elements of type T in point instances.

Each point also contains a Time instance, representing
a datetime stamp for the point. Time is a type defined
in the file Time.h. An application can extract both 
date and time information for each point.














 Operations:

The Point<T, N> class declares all of the special 
methods. The void constructor Point<T, N>() initializes
its T members to T{0} default values and sets tm to the
current date and time.

It also declares a constructor:
  Point<T, N>(std::initializer_list<T>)
that supports the syntax:
  Point<double, 4> p { 1.0, 1.5, 2.0, 1.5 };
and index operators for reading and writing specific 
coordinate values:
  T& operator[](size_t index);
const T operator[](size_t index); using syntax: p[0] = 1.5;
const double d2 = p[1]; The first returns a reference to p's value at index 0 and assigns it the value 1.5. The second simply returns a copy of the value at index 1. A point's datetime stamp can be retrieved using Time& time() and reset to current time with void updateTime().

2.5.3 Demonstrate Point<T, N>

Source code using Point<T, N> is shown here along with output generated when executed.
/*-- demonstrate use of Point type --*/

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

  println();
  showNote("Demo user-defined Point<T, N>", 40);

  /*-- demonstrate Point<double 3> initialization lists --*/
  showOp("Point<double, 3> p1 {1.0, 1.5, 2.0}");  // equal to N
  Point<double, 3> p1 {1.0, 1.5, 2.0};
  p1.show("p1");
  std::cout << "\n  p1[1] = " << p1[1];           // indexing
  std::cout << "\n  p1.time().day() = "
            << p1.time().day();
  std::cout << "\n  p1.time().seconds() = "
            << p1.time().seconds() << "\n";
  showOp("Point<double, 3> p2 {1.0, 1.5}");
  Point<double, 3> p2 {1.0, 1.5};                 // less than N
  p2.show("p2");
  showOp("Point<double, 3> p3 {1.0, 1.5, 2.0, 2.5}");
  Point<double, 3> p3 {1.0, 1.5, 2.0, 2.5};       // greater than N
  p3.show("p3");
  std::cout << "\n  p3.timeToString():\n    \""
            << p3.timeToString() << "\"\n";
  showOp("Point<int, 10> p3 { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }");
  Point<int, 10> p4 { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  p4.show("p4");
}
----------------------------------------
  Demo user-defined Point
----------------------------------------

  --- Point p1 {1.0, 1.5, 2.0} ---

  p1: Point {
    1, 1.5, 2
  }
  Thu May  9 20:10:50 2024 local time zone

  p1[1] = 1.5
  p1.time().day() = 9
  p1.time().seconds() = 50

  --- Point p2 {1.0, 1.5} ---

  p2: Point {
    1, 1.5, 0
  }
  Thu May  9 20:10:50 2024 local time zone

  --- Point p3 {1.0, 1.5, 2.0, 2.5} ---

  p3: Point {
    1, 1.5, 2
  }
  Thu May  9 20:10:50 2024 local time zone

  p3.timeToString():
    "Thu May  9 20:10:50 2024 local time zone"

  --- Point p3 { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } ---

  p4: Point {
    1, 2, 3, 4, 5, 6, 7,
    8, 9, 10
  }
  Thu May  9 20:10:50 2024 local time zone

                  

2.6 Demo Generic Functions

Generic functions are code generators; for each unique instantiated template parameter list the compiler generates a function, mapping the generic parameters to the parameters from the instantiated concrete types. template<T> f(T t) ----+--> f(int i) | +--> f(double d)

2.6.1 Generic Function Definitions

The code in this section illustrates how template resources are defined. Often template definitions will appear in a library, as is the case for Point<T, N>, which resides in "PointGen.h".
/*---------------------------------------------------------
  Generic Function Definitions
*/
/*---------------------------------------------------------
  showType(T t, ...)
    - Display calling name, static class, and size
    - requires DisplayParams
*/
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;
}
/*-------------------------------------------------------
  show sequential collection
  - requires integer indexer and size() function
  - works for any sequential STL collection
*/
template<typename C>
void showSeqColl(const C& c) {
  std::cout << "  Collection<T> [";
  std::cout << c[0];
  for(size_t i=1; i<c.size(); ++i) {
    std::cout << ", " << c[i];
  }
  std::cout << "]" << std::endl;
}
/*-------------------------------------------------------
  show associative collection
  - requires iterator
  - elements must be std::pair<Key, Value>
  - works for any associative STL collection
*/
template<typename C>
void showAssocColl(const C& c) {
  std::cout << "  Collection<K,V> {\n    ";
  bool first = true;
  for(const auto& pair : c) {
    if(first) {
      std::cout << "{" << pair.first << ", " << pair.second << "}";
      first = false;
    }
    else {
      std::cout << ", {" << pair.first << ", " << pair.second << "}";
    }
  }
  std::cout << "\n  }\n";
}

/*-----------------------------------------------
  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
  - used only in Point<T,N>::show()
*/
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 iterator, begin(), and end()
    like all the STL containers.
  - elements need overload for operator<< as
    implemented above
  - folds into rows with width elements
    - will replace folding logic with fold(...)
      eventually
*/
template<typename Coll>
std::string formatColl(
  const Coll& c, const std::string& nm, const std::string& suffix,
  size_t left, size_t width
) {
  std::stringstream out;
  out << indent(left) << nm << ": {\n" << indent(left + 2);
  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 << "\n" << indent(left) << "}" << 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
  - uses template metaprogramming, e.g., user code
    that evaluates during compilation
  - detects STL containers and user-defined types
    that provide iteration
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);
  }
}



---------------------------------------
  showType(T t, ...)
---------------------------------------
Arguments:
- generic type T, specified in using code
- string callname, name of t in caller's
  scope
- string suffix, usually either '' or '\n'
Operation:
- displays callname, truncated version of
  typeid(t).name, and suffix


---------------------------------------
  showSeqColl(const C& c)
---------------------------------------
  Arguments: 
    - generic type C, a collection type
  Operation displays:
    - prefix
    - first element value c[0]
    - comma followed by next element value c[i]
    - ...
    - suffix
  Requires of collection:
    - index operator
    - size() function
    - all STL containers have these

---------------------------------------
  showAssocColl(const C& c)
---------------------------------------
  Arguments: 
    - generic type C, a collection type
  Operation displays:
    - prefix
    - first element value { pair.first, pair.second }
    - comma followed by next element value 
    - ...
    - suffix
    - pair types are inferred from collection type
  Requires of collection:
    - iterator used in range for
    - elements are std::pair<T1, T2>
    - all STL containers have these
    - element types must have a std::operator<< 
      overload



---------------------------------------
  truncate(size_t N, const char* pStr)
---------------------------------------
  Arguments:
    - number of characters to retain in truncated string
    - literal string














---------------------------------------
  fold(
    std::vector<T>& v, 
    size_t left, size_t width
  ) 
---------------------------------------
  Arguments:
    - collection of elements to be folded into rows
    - left indentation in spaces
    - width of each row
  Operations:
    - creates in-memory stringstream
    - pushes left indent string
    - pushes vector elements into stringstream
      - inserts newline at end of row, determined by
        width
      - inserts element followed by comma if not last
      - ...
      - inserts last element
      - returns stringstreams internal string



---------------------------------------
  operator<<(
    std::stringstream& out,
    const std::pair<K,V>& p
  )
---------------------------------------
  Arguments:
    - std::stringstream collects output
    - std::pair<K,V> is argument to send to output
  Operations:
    - push pair into output
    - return output

---------------------------------------
  formatColl(
    const Coll& c,
    const std::string& nm,
    const std::string& suffix,
    size_t left, 
    size_t width
  )
---------------------------------------
  Arguments:
    - collection type c
    - caller name nm
    - suffix string ends output
    - number of indent spaces
    - number of elements per row
  Operations:
    - creates stringstream out
    - sends indent and name to out
    - sends elements to output
      - sends value followed by comma for all but last
      - pushes newline at end of row
    - pushes suffix to output
    - returns stringstream's internal string



---------------------------------------
  formatScalar(
    const T&t, 
    const std::string& nm, 
    const std::string& suffix,
    size_t left
  )
---------------------------------------
  Arguments:
    - const T&t scalar, e.g., single item
    - const std::string& nm, caller name
    - const std::string& suffix, last thing sent to ouput
    - indent in spaces
  Operations:
    - creates stringstream out
    - pushes indent, name, value of t, and suffix
    - returns out's internal string

---------------------------------------
  formatString(
    const T&t,
    const std::string& nm,
    const std::string& suffix,
    size_t left
  )
---------------------------------------

  Same as formatScalar except value t is enclosed
  with quotes.


---------------------------------------
  is_iterable_v<T>
---------------------------------------
  Template metaprogramming construct
  tests if T defines begin() and end()
  methods.

  That is true of all STL containers and 
  many user-defined containers

  This trait enables checking at compile-time
  if a collection can be iterated, as needed
  by range for.









---------------------------------------
  format(
    const T& t,
    const std::string& nm,
    const std::string& suffix,
    size_t left,
    size_t width
  )
---------------------------------------
  Decides at compile-time, using is_iterable_v<T>,
  whether to use formatColl or formatScalar.

  This function can format a large variety of scalars
  and collections, through the power of C++ generics.




2.6.2 Generic Functions Demonstration

The examples below illustrate how generic functions are instantiated and used in application code. Notice that some of these functions are powerful; they can be passed many different types. See especially the function format(const T& t, ...) which can display scalars and any collection that obeys the Standard Template Library (STL) rules.
/*---------------------------------------------------------
  Demonstrate functions
*/
void demo_generic_functions() {

  showNote("demo generic functions", nl);

  showOp("showType for std::string");
  std::string s = "a string";
  showType(s, "s", nl);

  showOp("showType for std::vector");
  std::vector<int> v {1, 2, 3, 2, 1, 0, -1, -2};
  showType(v, "v", nl);

  /*-------------------------------------------------------
    showSeqColl works for any collection with
    iterator, integer indexing, and size() function
  */
  showOp("showSeqColl for std::string", nl);
  showSeqColl(s);

  showOp("showSeqColl for std::vector", nl);
  showSeqColl(v);

  /*-------------------------------------------------------
    showAssocColl works for any collection with
    interator and std::pair<key, Value> elements
  */
  showOp("showAssocColl for std::map", "\n");
  std::map<std::string, int> m {
    {"zero", 0}, {"one", 1}, {"two", 2}, {"three", 3},
    {"four", 4}, {"five", 5}
  };
  showAssocColl(m);

  showOp("showAssocColl for std::unordered_map", "\n");
  std::unordered_map<std::string, int> um1 {
    {"zero", 0}, {"one", 1}, {"two", 2}, {"three", 3},
    {"four", 4}, {"five", 5}
  };
  showAssocColl(um1);

  /*-------------------------------------------------------
    Analysis::format works for any collection with
    interator and std::pair<key, Value> elements
  */
  showOp("Analysis::format for int");
  int mol = 42;
  std::cout << Analysis::format(42, "mol", "\n");
  // need to distinguish Analysis::format from std::format

  showOp("Analysis::format for std::string");
  std::cout << Analysis::format(s, "s", "\n");

  showOp("Analysis::format for std::vector");
  std::cout << Analysis::format(v, "v", "\n", 2, 5);

  showOp("Analysis::format for std::map");
  std::cout << Analysis::format(m, "m", "\n", 2, 4);

  showOp("Analysis::format for std::unordered_map");
  std::unordered_map<std::string, int> um2 {
    {"zero", 0}, {"one", 1}, {"two", 2}, {"three", 4},
    {"four", 4}, {"five", 5}
  };
  std::cout << Analysis::format(um2, "um", "\n", 2, 4);
}




  --------------------------------------------------
    demo generic functions
  --------------------------------------------------

  --- showType for std::string ---
  s type: class std::basic_string<char,struct std:...
  size:  28

  --- showType for std::vector ---
  v type: class std::vector<int,class std::allocat...
  size:  16

  --- showSeqColl for std::string ---
  Collection<T> [a,  , s, t, r, i, n, g]

  --- showSeqColl for std::vector ---
  Collection<T> [1, 2, 3, 2, 1, 0, -1, -2]

  --- showAssocColl for std::map ---
  Collection<K,V> {
    {five, 5}, {four, 4}, {one, 1}, {three, 3}, {two, 2}, {zero, 0}
  }

  --- showAssocColl for std::unordered_map ---
  Collection<K,V> {
    {three, 3}, {zero, 0}, {one, 1}, {two, 2}, {five, 5}, {four, 4}
  }

  --- Analysis::format for int ---
  mol: 42

  --- Analysis::format for std::string ---  s: {
    a,  , s, t, r, i, n,
    g
  }

  --- Analysis::format for std::vector ---  v: {
    1, 2, 3, 2, 1,
    0, -1, -2
  }

  --- Analysis::format for std::map ---  m: {
    {five, 5}, {four, 4}, {one, 1}, {three, 3},
    {two, 2}, {zero, 0}
  }

  --- Analysis::format for std::unordered_map ---  um: {
    {three, 4}, {zero, 0}, {one, 1}, {two, 2},
    {five, 5}, {four, 4}
  }














2.7 Time and Timer

An instance of the Time class is used by Point<T, N> to provide datetime stamp services, but has additional calendar services that will be useful for other applications.
Time  

2.7.1 Time Definition

The Time class is a non-generic type that captures and parses date and time. It can display its results in either local time or GMT. It has methods to retreive the current datetime stamp and individual components like day() and hour().
/*-------------------------------------------------------------------
  Time.h defines Time class to manage datetime strings
  - Uses chrono to implement class for updateable time instances

  Note: Add callable function for end of time period
*/
#include <iostream>
#include <string>
#include <chrono>
#include <ctime>

namespace Points {

  /*---------------------------------------------
    Time manages calendar times
  */
  class Time {
    public:
      Time();
      time_t getTime();
      tm getLocalTime();
      tm getGMTTime();
      std::string getTimeZone();
      std::string toString();
      size_t year();
      size_t month();
      size_t day();
      size_t hour();
      size_t minutes();
      size_t seconds();
    private:
      std::chrono::time_point<std::chrono::system_clock> tp;
      std::tm calTime;
      std::string dateTimeSuffix;
  };
  /*-----------------------------------------------
    Construct instance holding time_point for
    std::chrono::system_clock's epoch
    - epoch is number of seconds since 1 January 1970 UTC
    - epoch may vary with clock, e.g.,
      system_clock, high_resolution_clock
    - time_point is a structure holding chrono::duration
      for the clock's epoch
  */
  Time::Time() {
    tp = std::chrono::system_clock::now();
    calTime = getLocalTime();
  }
  /*-----------------------------------------------
    time_t is an integral type holding number of
    seconds in the current time_point
  */
  std::time_t Time::getTime() {
    return std::chrono::system_clock::to_time_t(tp);
  }
  /*-----------------------------------------------
    returns datetime string
    - Wed Feb 21 10:18:12 2024 local_time_zone
  */
  std::string Time::toString() {
    struct tm time;
    time_t tt = getTime();
    /* compute for GMT zone */
    if(dateTimeSuffix == "GMT") {
      gmtime_s(&time, &tt);
    }
    /* compute for local time zone*/
    else {
      localtime_s(&time, &tt);
    }
    std::string rs = asctime(&time);
    rs.resize(rs.size() - 1);  // remove trailing newline
    rs += " " + dateTimeSuffix;
    return rs;
  }
  /*-----------------------------------------------
    tm is structure holding components of calendar
    date and time, e.g., tm_sec, tm_min, ...
    - member calTime is localtime after calling
      this function
  */
  tm Time::getLocalTime() {
    time_t tt = getTime();
    localtime_s(&calTime, &tt);  // save in calTime
    dateTimeSuffix = "local time zone";
    return calTime;
  }
  /*-----------------------------------------------
    tm is structure holding components of calendar
    date and time, e.g., tm_sec, tm_min, ...
    - member calTime is gmttime after calling
      this function
  */
  tm Time::getGMTTime() {
    time_t tt = getTime();
    gmtime_s(&calTime, &tt);  // save in calTime
    dateTimeSuffix = "GMT";
    return calTime;
  }
  /*---------------------------------------------
    methods to retrieve dateTime components
  */
  std::string Time::getTimeZone() {
    return dateTimeSuffix;
  }
  size_t Time::year() {
    auto yr = calTime.tm_year + 1900;
    return yr;
  }
  size_t Time::month() {
    auto mn = calTime.tm_mon + 1;
    return mn;
  }
  size_t Time::day() {
    auto d = calTime.tm_mday;
    return d;
  }
  size_t Time::hour() {
    auto hr = calTime.tm_hour;
    return hr;
  }
  size_t Time::minutes() {
    auto min = calTime.tm_min;
    return min;
  }
  size_t Time::seconds() {
    double sec = calTime.tm_sec;
    return sec;
  }
}
Time:

The Time class provides formatted datetime strings, 
referred here by the term datetime stamps. These provide
local calendar and time like this:

  Wed Feb 28 20:35:23 2024 local time zone

It also provides methods to access local date and 
time components like day() and seconds() and to switch 
between local time and GMT.

Time uses the std::chrono::system_clock for all date and
time evaluations. Its epoch is elapsed time since 
1 January 1970, measured in ticks which are platform 
defined number of seconds.

Methods getLocalTime() and getGMTTime() determine 
whether toString() and the components day() and hour()
are based on local time or on Greenwich Mean Time, GMT.

2.7.2 Demo Time

Time is used by Point<T, N> to provide datetime services for points. The value of the Time member is interpreted as a time at which some application specific thing was at the point described by its dimensional member.
/*-------------------------------------------------------------------*/
void testtime() {
  showNote("test Time","\n");
  Time t;
  t.getLocalTime();
  std::cout << "\n  datetime = " << t.toString() << std::endl;
  std::cout << "\n  epoch in secs = " << t.getTime();
  std::cout << "\n  year:     " << t.year();
  std::cout << "\n  month:    " << t.month();
  std::cout << "\n  day:      " << t.day();
  std::cout << "\n  hour:     " << t.hour();
  std::cout << "\n  minutes:  " << t.minutes();
  std::cout << "\n  seconds:  " << t.seconds();
  std::cout << "\n  timezone: " << t.getTimeZone();
  std::cout << std::endl;

  t.getGMTTime();
  std::cout << "\n  datetime = " << t.toString() << std::endl;
  std::cout << "\n  epoch in secs = " << t.getTime();
  std::cout << "\n  year:     " << t.year();
  std::cout << "\n  month:    " << t.month();
  std::cout << "\n  day:      " << t.day();
  std::cout << "\n  hour:     " << t.hour();
  std::cout << "\n  minutes:  " << t.minutes();
  std::cout << "\n  seconds:  " << t.seconds();
  std::cout << "\n  timezone: " << t.getTimeZone();
  std::cout << std::endl;
}

  --------------------------------------------------
    test Time
  --------------------------------------------------

  datetime = Wed Feb 28 20:35:23 2024 local time zone

  epoch in secs = 1709174123
  year:     2024
  month:    2
  day:      28
  hour:     20
  minutes:  35
  seconds:  23
  timezone: local time zone

  datetime = Thu Feb 29 02:35:23 2024 GMT

  epoch in secs = 1709174123
  year:     2024
  month:    2
  day:      29
  hour:     2
  minutes:  35
  seconds:  23
  timezone: GMT

                  
The Timer class is used by demonstrations in "Cpp_Generics.cpp" to explore the resolution and accuracy of system time measurements.
Timer 

2.7.3 Timer Definition

The Timer class is a non-generic type that measures elapsed type with the std::chrono::high_resolution_clock. It has methods to start and stop the timer and retrieve elapsed time in milliseconds, microseconds, and nanoseconds.
/*-------------------------------------------------------------------
  Timer provides elapsed time services
*/
class Timer {
  public:
  Timer();
  void start();
  void stop();
  size_t elapsedNanoSec();
  size_t elapsedMicroSec();
  size_t elapsedMilliSec();
  private:
    std::chrono::time_point<
      std::chrono::high_resolution_clock
    > tp;
    std::chrono::time_point<
      std::chrono::high_resolution_clock
    > starttime;
    std::chrono::time_point<
      std::chrono::high_resolution_clock
    > stoptime;
};
Timer::Timer() {
  starttime = std::chrono::high_resolution_clock::now();
  stoptime = std::chrono::high_resolution_clock::now();
}
void Timer::start() {
  starttime = std::chrono::high_resolution_clock::now();
}
void Timer::stop() {
  stoptime = std::chrono::high_resolution_clock::now();
}
size_t Timer::elapsedNanoSec() {
  auto duration = 
    duration_cast<std::chrono::nanoseconds>(stoptime - starttime);
  return duration.count();
}
size_t Timer::elapsedMicroSec() {
  auto duration = 
    duration_cast<std::chrono::microseconds>(stoptime - starttime);
  return duration.count();
}
size_t Timer::elapsedMilliSec() {
  auto duration = 
    duration_cast<std::chrono::milliseconds>(stoptime - starttime);
  return duration.count();
}
Timer:

Timer uses the std::chrono::high_resolution_clock for 
all time evaluations. Its epoch is elapsed time since 
1 January 1970, measured in ticks which are platform 
defined number of nanoseconds.

Timer defines stopwatch functionality with start() and
stop() methods.

Time interval results are accessed with elapsedNanoSec(),
elapsedMicroSec(), and elapsedMilliSec() methods.

2.7.2 Demo Timer

Timing data are not likely to be repeatable; the operating system starts and stops tasks, network I/O processes messages, and users create UI events, all of which take CPU resources at essentially random times. This demonstration repeats timing tasks four times to show the kinds of variations in measurements that are likely during each run. In each run, we start and then stop the timer immediately to test, with a noop, the finest granularity of time we can measure. A computational task is run 200 times for each task measurement, and a thread is blocked for a specified interval and measured for each of the four runs.
/*-------------------------------------------------------------------*/
void testtimer() {

  showNote("test Timer");

  std::vector<double> v {
    1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5
  };
  /*-------------------------------------------------------
    lambda that squares each element of the collection v
  */
  auto f = [&v]() {
    for(auto &item : v) { item *= item; }
  };
  /*-------------------------------------------------------
    lambda g executes lambda f for n times.
  */
  auto g = [f](size_t n) {
    for(size_t i = 0; i < n; ++i) { f(); }
  };
  /*-------------------------------------------------------
    Timer test
  */
  Timer tmr;
  tmr.start();
  tmr.stop();
  std::cout << "\n  noOp elapsed interval in nanosec = " << tmr.elapsedNanoSec();

  tmr.start();
  g(200);
  tmr.stop();
  std::cout << "\n  g(200) elapsed interval in nanosec = " << tmr.elapsedNanoSec();
  std::cout << "\n  g(200) elapsed interval in microsec = " << tmr.elapsedMicroSec();

  tmr.start();
  std::this_thread::sleep_for(std::chrono::milliseconds(5));
  tmr.stop();
  std::cout << "\n  5 millisec sleep elapsed interval in millisec = " << tmr.elapsedMilliSec();
  std::cout << std::endl;
}

  --------------------------------------------------
    test Timer
  --------------------------------------------------
  noOp elapsed interval in nanosec = 0
  g(200) elapsed interval in nanosec = 2300
  g(200) elapsed interval in microsec = 2
  5 millisec sleep elapsed interval in millisec = 5

  --------------------------------------------------
    test Timer
  --------------------------------------------------
  noOp elapsed interval in nanosec = 200
  g(200) elapsed interval in nanosec = 10900
  g(200) elapsed interval in microsec = 10
  5 millisec sleep elapsed interval in millisec = 12

  --------------------------------------------------
    test Timer
  --------------------------------------------------
  noOp elapsed interval in nanosec = 100
  g(200) elapsed interval in nanosec = 9600
  g(200) elapsed interval in microsec = 9
  5 millisec sleep elapsed interval in millisec = 8

  --------------------------------------------------
    test Timer
  --------------------------------------------------
  noOp elapsed interval in nanosec = 100
  g(200) elapsed interval in nanosec = 7300
  g(200) elapsed interval in microsec = 7
  5 millisec sleep elapsed interval in millisec = 11







                  
Neither of these classes is generic, and so we provide them as details that may be shown, but are not by default.

2.8 Analysis and Display Functions

This block presents functions for analysis and display of instances of generic types. Many of them were discussed in some detail in the section 1.6 Demo Generic Functions. There are a few functions, not treated there, but are relatively simple. The code doesn't introduce any new information about generics, and so is not shown by default.
Analysis and Display Functions 
/*---------------------------------------------------------
  AnalysisGen.h
  - Provides functions that analyze types, display results
    and other program defined information.
  - Some of this code requires complex template operations.
    Those will be discussed in the generics bit.
  - You can skip the hard parts until then, without loss
    of understanding.
*/

#include <typeinfo>     // typeid
#include <utility>      // move()
#include <sstream>      // stringstream
#include <type_traits>  // is_scalar, if constexpr
#include <iostream>     // cout
#include <vector>       // vector

namespace Analysis {
  /*------------------------------------------------------------
    Analysis function declarations are provided here so that
    definitions below may be placed in any order. That's
    needed because C++ requires declaration before use.
  */
  template<typename T, int N>
  void showArray(std::array<T,N> &a);
  template<typename C>
  void showColl(const C& c);
  template<typename K, typename V>
  void showMap(const std::map<K,V> &m);
  template<typename T>
  void showType(T t, const std::string &nm, const std::string& suffix = "");
  void showNote(const std::string& txt, const std::string& suffix = "");
  void showOp(const std::string& opstr, const std::string& suffix = "");
  void print(const std::string& txt = "");
  void println(const std::string& txt = "");
  std::string truncate(size_t N, const char* pStr);
  std::string indent(size_t n);
  template<typename T>
  std::string fold(std::vector<T>& v, size_t left, size_t width);
  template<typename T>
  std::string formatColl(
    const T& t, const std::string& nm,
    const std::string& suffix = "", size_t left = 2, size_t width = 7
  );
  template<typename T>
  std::string formatScalar(
    const T& t, const std::string& nm,
    const std::string& suffix = "", size_t left = 2
  );
  template<typename T>
  std::string formatString(
    const T& t, const std::string& nm, const std::string& suffix,
    size_t left = 2
  );
  template<typename T>
  std::string format(
    const T& t, const std::string& nm, const std::string& suffix = "",
    size_t left = 2, size_t width = 7
  );
  /*-- end of function declarations --*/

  /*------------------------------------------------------------
    Display and Analysis functions and global 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

   /*------------------------------------------------------
    Demonstration functions
  */
  /*-----------------------------------------------
    Display calling name, static class, and size
    - requires DisplayParams
  */
  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;
  }
  /*-------------------------------------------------------
   showArray function -- specific to std::array
  */
  template<typename T, int N>
  void showArray(std::array<T,N> &a) {
    std::cout << "  array<T,N> [";
    std::cout << a[0];
    for(int i=1; i<N; ++i) {
      std::cout << ", " << a[i];
    }
    std::cout << "]" << std::endl;
  }
  /*-------------------------------------------------------
    show sequential collection
    - requires integer indexer and size() function
    - works for any sequential STL collection
  */
  template<typename C>
  void showSeqColl(const C& c) {
    std::cout << "  Collection<T> [";
    std::cout << c[0];
    for(size_t i=1; i<c.size(); ++i) {
      std::cout << ", " << c[i];
    }
    std::cout << "]" << std::endl;
  }
  /*-------------------------------------------------------
    show associative collection
    - requires iterator
    - elements must be std::pair<Key, Value>
    - works for any associative STL collection
  */
  template<typename C>
  void showAssocColl(const C& c) {
    std::cout << "  Collection<K,V> {\n    ";
    bool first = true;
    for(const auto& pair : c) {
      if(first) {
        std::cout << "{" << pair.first << ", " << pair.second << "}";
        first = false;
      }
      else {
        std::cout << ", {" << pair.first << ", " << pair.second << "}";
      }
    }
    std::cout << "\n  }\n";
  }

  /*-----------------------------------------------
    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
    - used only in Point<T,N>::show()
  */
  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 iterator, begin(), and end()
      like all the STL containers.
    - elements need overload for operator<< as
      implemented above
    - folds into rows with width elements
      - will replace folding logic with fold(...)
        eventually
  */
  template<typename Coll>
  std::string formatColl(
    const Coll& c, const std::string& nm, const std::string& suffix,
    size_t left, size_t width
  ) {
    std::stringstream out;
    out << indent(left) << nm << ": {\n" << indent(left + 2);
    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 << "\n" << indent(left) << "}" << 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
    - uses template metaprogramming, e.g., user code
      that evaluates during compilation
    - detects STL containers and user-defined types
      that provide iteration
  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 emphasized text
  */
  inline void showNote(const std::string& txt, const std::string& suffix) {
    print("--------------------------------------------------");
    print("  " + txt);
    print("--------------------------------------------------");
    std::cout << suffix;
  }
  /*-----------------------------------------------
    Display emphasized line
  */
  inline void showOp(const std::string& opstr, const std::string& suffix) {
    std::cout << "\n  --- " << opstr << " ---" << suffix;
  }
  /*-----------------------------------------------
    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 & Display

This code defines fifteen functions. Nine of them are 
generic, and three are helpers that applications will 
not call directly.

The code starts with declarations for each function, 
so they can be defined in any order. That is necessary 
since C++ demands that declaration comes before 
definition. That means that one function cannot be used
by another unless the compiler has already seen its 
declaration.

Formatting:

A shared global data structure struct displayParams 
is used to pass formatting for indentation and width of 
rows for folded items to functions.

We try to avoid shared globals as they are a frequent 
source of bugs.  They can have effects on functions 
without them acknowledging use in their function
signature. In this case, we have no choice because we 
need to affect formatting implemented by 
operator<<(std::ostream& out, collection_type)
and cannot change its signature.

Abstraction:

Consider the function std::string format(const T&t, ...) 
near the bottom of the left code panel. That function
can display any type that is either scalar or iterable. 
That is a very powerful facililty.  It means we don't 
have to build many functions to display the wide variety 
of types we use in applications.

3.0 Build

In the dropdown you will find output emitted by CMake during a build for this code.
Build  

C:\github\JimFawcett\Bits\Cpp\Cpp_Generics\build
> cmake ..
-- The CXX compiler identification is MSVC 19.39.33218.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.39.33218/bin/Hostx64/x64/cl.exe - skipped   
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.39.33218/bin/Hostx64/x64/cl.exe - skipped 
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (2.9s)
-- Generating done (0.0s)
-- Build files have been written to: C:/github/JimFawcett/Bits/Cpp/Cpp_Generics/build
C:\github\JimFawcett\Bits\Cpp\Cpp_Generics\build
> cmake --build .
MSBuild version 17.9.0-preview-23551-05+34ae4f308 for .NET Framework

  1>Checking Build System
  Building Custom Rule C:/github/JimFawcett/Bits/Cpp/Cpp_Generics/CMakeLists.txt
  Bits_Generics.cpp
  Cpp_Generics.vcxproj -> C:\github\JimFawcett\Bits\Cpp\Cpp_Generics\build\Debug\Cpp_Generics.exe
  Building Custom Rule C:/github/JimFawcett/Bits/Cpp/Cpp_Generics/CMakeLists.txt
C:\github\JimFawcett\Bits\Cpp\Cpp_Generics\build

4.0 Visual Studio Code View

The code for this demo is available in github.com/JimFawcett/Bits/Cpp_Generics/src. If you go up to the Bits Repository and 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_Generics. Figure 1. VS Code IDE Figure 2. Launch.JSON

5.0 References

Reference Description
C++ Story E-book with thirteen chapters covering most of intermediate C++
C++ Templates Templates chapter from C++ Story
Template Metaprogramming Template Metaprogramming chapter from C++ Story
C++ Bites Relatively incomplete list of short feature discussions
STRCode User-defined string type with all standard methods and functions documented with: purpose, declaration, definition, invocation, and notes.
w3schools tutorial Slow and easy walk throught basics.
cppreference.com     Very complete reference with lots of details and examples.
>