about
Bits Content Prototype
05/10/2024
0
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
1.0 Templates and Concepts
Examples in Section 2.6.1
auto f<T1, T2> (t:T1, ...) -> T2 {
/* implementing code using T1 and T2 */
}
Examples in Section 2.5
class Point
{
/* code declaring Point methods and
data with N items of type 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 */ }
Example in Section 2.4.1
concept Show = requires (T t, const std::string& s) {
t.show(s)
};
-
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. -
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.
- C++ Templates, The Complete Guide, Second Edition, Vandevoorde, Josuttis, Gregor
- 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.
- That means that each included file must contain, directly, or indirectly, all of the template definitions on which it depends.
2.0 Source Code
2.1 Demo Standard Library Generic Types
/*-------------------------------------------------------------------*/
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}
}
2.2 User-defined Class: HelloTemplates<T>
2.2.1 HelloTemplates<T> Definition
/*-------------------------------------------------------------------
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;
};
2.2.2 HelloTemplates<T> Demo
/*-- 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.3 User-defined Class: Stats<T>
2.3.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 class contains a vector of numbers. Its methodscompute simple statistics on that collection to demon-
strate how arithmethic operations work on generic types.
This example illustrates the use of C++ Concepts intro-
duced in C++20.
⇐ C++ concept definition:
⇐ Stats<T> class
contains ato carry out numerical operations on them, e.g.,
The class, and each of its methods requires template para-
meter
an integral or floating point type.
Template Compilation:
All templates are compiled in two phases. The first checkstemplate 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
if and only if all of the member function invocations on
methods of
So the second compilation phase will succeed for
if and only if
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.3.2 Stats<T> Demonstration
/*-- 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.4 User-defined Class: Point<T, N>
2.4.1 Point<T, N> Definition
/*-------------------------------------------------------------------
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:
weather event at a given time, e.g., latitude, longitude, series
of altitudes, wind velocities, dewpoint, ...
chemical process, e.g. temperature, pressure, sequence of con-
centrations of molecules, ...
Purpose:
template parameters.
time stamp for the point. Time is a type defined in the file
Time.h. An application can extract both date and time inform-
ation for each point.
⇐ Operations:
The void constructor
to
that supports the syntax:
and index operators for reading and writing specific coordinate
values:
using syntax:
The first returns a reference to
it the value 1.5. The second simply returns a copy of the value
at index 1.
and reset to current time with
2.4.2 Demonstrate Point<T, N>
/*-- 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.5 Demo Generic Functions
2.5.1 Generic Function Definitions
/*---------------------------------------------------------
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);
}
}
Generic Functions:
---------------------------------------
---------------------------------------
Arguments:
- generic type
- string callname, name of t in caller's scope:
- string suffix, usually either ' ' or '\n'
Operation:
- displays callname, truncated version of
---------------------------------------
---------------------------------------
Arguments:
- generic type
Operation displays:
- prefix
- first element value
- comma followed by next element value
- suffix
Requires of collection:
- index operator
-
- all STL containers have these
--------------------------------------------
--------------------------------------------
Arguments:
- generic type
Operation displays:
- prefix
- first element value
- comma followed by next element value, ...
- suffix
- pair types are inferred from collection type
Requires of collection:
- iterator used in range-for
- elements are
- all STL containers have these
- element types must have a
overload
--------------------------------------------------------
--------------------------------------------------------
Arguments:
- number of characters to retain in truncated string
- literal string
--------------------------------------------
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
- pushes left indent
- pushes vector elements into
- inserts newline at end of row, determined by width
- inserts element followed by comma if not last
- ...
- inserts last element
- returns stringstreams internal string
---------------------------------------
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
---------------------------------------
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.5.2 Generic Functions Demonstration
/*---------------------------------------------------------
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.6 Time and Timer
Time
2.6.1 Time Definition
/*-------------------------------------------------------------------
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:
Thereferred 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
between local time and GMT.
⇐
uses the
and time evaluations. Its epoch is elapsed time since
1 January 1970, measured in ticks which are a platform
defined number of seconds.
⇐ Methods
whether
and
Greenwich Mean Time, GMT.
2.6.2 Demo Time
/*-------------------------------------------------------------------*/
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
Timer
2.6.3 Timer Definition
/*-------------------------------------------------------------------
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:
⇐
uses the
all time evaluations. Its epoch is elapsed time since
1 January 1970, measured in ticks which are platform
defined number of nanoseconds.
⇐
defines stopwatch functionality with
methods.
⇐ Time interval
Time interval results are accessed with
2.6.4 Demo Timer
/*-------------------------------------------------------------------*/
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
2.7 Analysis and Display Functions
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 aregeneric, 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 structureis 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
and cannot change its signature.
Abstraction:
Consider functionnear 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.
2.8 Program Structure
/*---------------------------------------------------------
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:
⇐ 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
this demonstration program.
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, arebeing 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
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.
3.0 Build
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
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. |
Purpose:
template syntax.
⇐ Basic Syntax:
Each generic class and function must be preceeded by atemplate declaration:
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.
generate two unique classes.
and displays a single instance of the type
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
instance on its composed instance of T.
⇐ Specialization:
Template classes can have a specialized method for a specifiedtype. Specialization is used when a method needs to treat the
specified type differently than for all of the other generic types.
The C++ language guarantees that when the template type
is a specialized type it will call the specialized method. If not a
specialized type the generic method is called.