#include <vector>
template<typename T, typename CONT = std::vector<T>>
class stack {
public:
stack();
~stack();
auto push(T&) -> void;
auto pop() -> void;
auto top() -> T&;
auto top() const -> T const&;
static int num_stacks_;
private:
CONT stack_;
};
template<typename T, typename CONT>
int stack<T, CONT>::num_stacks_ = 0;
template<typename T, typename CONT>
stack<T, CONT>::stack() {
num_stacks_++;
}
template<typename T, typename CONT>
stack<T, CONT>::~stack() {
num_stacks_--;
}
#include <iostream>
#include "./demo801-default.h"
auto main() -> int {
auto fs = stack<float>{};
stack<int> is1, is2, is3;
std::cout << stack<float>::num_stacks_ << "\n";
std::cout << stack<int>::num_stacks_ << "\n";
auto fl = stack<float, std::list<float>>{};
}
demo801-default.h
demo801-default.cpp
template<class T, class U = int> class A;
template<class T = float, class U> class A;
template<class T, class U> class A {
public:
T x;
U y;
};
A<> a;
a.x ?
a.y?
If one template parameter has a default argument, then all template parameters following it must also have default arguments.
template<class T = char, class U, class V = int> class X { };
template<class T = char, class U, class V = int, class W> class X { };
//not allowed
template<class T, class U = char> class A
{
public:
T x;
U y;
};
int main()
{
A<char> a;
A<int, int> b;
std::cout<<sizeof(a)<<std::endl;
std::cout<<sizeof(b)<<std::endl;
return 0;
}
#include <vector>
#include <iostream>
#include <numeric>
template <typename T>
class stack {
public:
auto push(T t) -> void { stack_.push_back(t); }
auto top() -> T& { return stack_.back(); }
auto pop() -> void { stack_.pop_back(); }
auto size() const -> int { return stack_.size(); };
auto sum() -> int {
return std::accumulate(stack_.begin(), stack_.end(), 0);
}
private:
std::vector<T> stack_;
};
auto main() -> int {
int i1 = 6771;
int i2 = 1917;
stack<int> s1;
s1.push(i1);
s1.push(i2);
std::cout << s1.size() << " ";
std::cout << s1.top() << " ";
std::cout << s1.sum() << "\n";
}
template <typename T>
class stack<T*> {
public:
auto push(T* t) -> void { stack_.push_back(t); }
auto top() -> T* { return stack_.back(); }
auto pop() -> void { stack_.pop_back(); }
auto size() const -> int { return stack_.size(); };
auto sum() -> int{
return std::accumulate(stack_.begin(),
stack_.end(), 0, [] (int a, T *b) { return a + *b; });
}
private:
std::vector<T*> stack_;
};
#include "./demo802-partial.h"
auto main() -> int {
auto i1 = 6771;
auto i2 = 1917;
auto s1 = stack<int>{};
s1.push(i1);
s1.push(i2);
std::cout << s1.size() << " ";
std::cout << s1.top() << " ";
std::cout << s1.sum() << "\n";
}
demo802-partial.h
demo802-partial.cpp
#include "./demo802-partial.h"
auto main() -> int {
auto i1 = 6771;
auto i2 = 1917;
auto s1 = stack<int>{};
s1.push(i1);
s1.push(i2);
std::cout << s1.size() << " ";
std::cout << s1.top() << " ";
std::cout << s1.sum() << "\n";
}
#include "./demo802-partial.h"
auto main() -> int {
auto i1 = 6771;
auto i2 = 1917;
auto s1 = stack<int>{};
s1.push(i1);
s1.push(i2);
std::cout << s1.size() << " ";
std::cout << s1.top() << " ";
std::cout << s1.sum() << "\n";
}
auto s2 = stack<int*>{};
s2.push(&i1);
s2.push(&i2);
std::cout << s2.size() << " ";
std::cout << *(s2.top()) << " ";
std::cout << s2.sum() << "\n";
https://www.youtube.com/watch?v=7SqySe4Lkow&ab_channel=CppNuts
#include<iostream>
#include<sstream>
#include<vector>
template<typename T>
T add_all(const std::vector<T> &list) {
T accumulator = {};
for (auto& elem:list){
accumulator += elem;
}
return accumulator;
}
template<>
T add_all(const std::vector<std::string> &list) {
std::string accumulator = {};
for (const std::string& elem : list)
for (const char& chr : elem)
accumulator += elem;
}
return accumulator;
}
int main() {
std::vector<int> ivec = {4,3,2,4};
std::vector<double> dvec = {4.0,3.0,2.0,4.0};
std::vector<string> svec = {"abc", "bcd"};
std::cout << add_all(ivec) << std::endl;
std::cout << add_all(dvec) << std::endl;
std::cout << add_all(svec) << std::endl;
}
#include <iostream>
template <typename T>
void fun(T a) {
std::cout << "The main template fun(): "
<< a << std::endl;
}
template<> // may be skipped, but prefer overloads
void fun(int a) {
std::cout << "Explicit specialisation for int type: "
<< a << std::endl;
}
int main() {
fun<char>('a');
fun<int>(10);
fun<float>(10.14);
}
#include <iostream>
template <class T>
class Test {
// Data members of test
public:
Test() {
// Initialization of data members
cout << "General template object \n";
}
// Other methods of Test
};
template <>
class Test<int> {
public:
Test() {
// Initialization of data members
std::cout << "Class template specialisation\n";
}
};
int main() {
Test<int> a;
Test<char> b;
Test<float> c;
return 0;
}
#include <iostream>
template <typename T>
struct is_void {
static bool const val = false;
};
template<>
struct is_void<void> {
static bool const val = true;
};
auto main() -> int {
std::cout << is_void<int>::val << "\n";
std::cout << is_void<void>::val << "\n";
}
demo803-explicit.cpp
Trait: Class (or class template) that characterises a type
Compile time template metafunctions that returns the info about types.
Ask compiler some question? is it int, fun, class or pointer or will it throw exception?
Why ?: Optimization: quick sort /merge sort by knowing data type of iterator or const to non
type trait is a simple struct template that contains a member constant, which in turn holds the answer to the question the type trait asks or the transformation it performs
Useful in conditional compilation or
you can apply transformation i.e. add or remove const
handy when writing libraries that make use of template
Advantage: All at compile time, nothing run time
Trait Categoris:
Numeric_Limit<>:: min,max, is_signed, is_integer
Type Category: is_array<T>::value, is_pointer, is_enum
Type Properties: is_const
#include <iostream>
#include <limits>
auto main() -> int {
std::cout << std::numeric_limits<double>::min() << "\n";
std::cout << std::numeric_limits<int>::min() << "\n";
}
template <typename T>
struct numeric_limits {
static auto min() -> T;
};
template <>
struct numeric_limits<int> {
static auto min() -> int { return -INT_MAX - 1; }
}
template <>
struct numeric_limits<float> {
static auto min() -> float { return -FLT_MAX - 1; }
}
This is what <limits> might look like
Traits allow generic template functions to be parameterised
#include <array>
#include <iostream>
#include <limits>
template<typename T, std::size_t size>
T findMax(const std::array<T, size>& arr) {
T largest = std::numeric_limits<T>::min();
for (auto const& i : arr) {
if (i > largest)
largest = i;
}
return largest;
}
auto main() -> int {
auto i = std::array<int, 3>{-1, -2, -3};
std::cout << findMax<int, 3>(i) << "\n";
auto j = std::array<double, 3>{1.0, 1.1, 1.2};
std::cout << findMax<double, 3>(j) << "\n";
}
demo804-typetraits1.cpp
#include <type_traits>
class GFG {
};
// main method
auto main() -> int {
{
std::cout << alignment_of<GFG>::value << std::endl;
std::cout << alignment_of<int>() << std::endl;
std::cout << alignment_of<double>() <<std::endl;
return 0;
}
#include <iostream>
template <typename T>
struct is_void {
static const bool val = false;
};
template<>
struct is_void<void> {
static const bool val = true;
};
auto main() -> int {
std::cout << is_void<int>::val << "\n";
std::cout << is_void<void>::val << "\n";
}
#include <iostream>
template <typename T>
struct is_pointer {
static const bool val = false;
};
template<typename T>
struct is_pointer<T*> {
static const bool val = true;
};
auto main() -> int {
std::cout << is_pointer<int*>::val << "\n";
std::cout << is_pointer<int>::val << "\n";
}
demo805-typetraits2.cpp
demo806-typetraits3.cpp
template<template T>
typename std::remove_refernce<T>::type doSomething(T value)
{return value;}
const T& | const T | |
---|---|---|
T & | T | |
T&& | T | do more than conversion |
const T | T | |
T | T |
#include <iostream>
#include <type_traits>
template<typename T>
auto testIfNumberType(T i) -> void {
if (std::is_integral<T>::value || std::is_floating_point<T>::value) {
std::cout << i << " is a number"
<< "\n";
}
else {
std::cout << i << " is not a number"
<< "\n";
}
}
auto main() -> int {
auto i = int{6};
auto l = long{7};
auto d = double{3.14};
testIfNumberType(i);
testIfNumberType(l);
testIfNumberType(d);
testIfNumberType(123);
testIfNumberType("Hello");
auto s = "World";
testIfNumberType(s);
}
demo807-typetraits4.cpp
#include <iostream>
#include <type_traits>
void algorithm_signed (int i) { /*...*/ }
void algorithm_unsigned(unsigned u) { /*...*/ }
template <typename T>
void algorithm(T t) // act as dispatcher
{
if constexpr(std::is_signed<T>::value)
algorithm_signed(t);
else
if constexpr (std::is_unsigned<T>::value)
algorithm_unsigned(t);
else
static_assert(std::is_signed<T>::value
|| std::is_unsigned<T>::value, "Must be signed or unsigned!");
}
int main(){
algorithm(3); // T is int, include algorithm_signed()
unsigned x = 3;
algorithm(x); // T is unsigned int, include algorithm_unsigned()
algorithm("hello"); // T is string, build error!
}
These are the instantiations that will have been generated
#include <iostream>
#include <typeinfo>
template <typename T>
auto print(const T& msg) -> void {
std::cout << msg << " ";
}
template <typename A, typename... B>
auto print(A head, B... tail) -> void {
print(head);
print(tail...);
}
auto main() -> int {
print(1, 2.0f);
std::cout << "\n";
print(1, 2.0f, "Hello");
std::cout << "\n";
}
auto print(const char* const& c) -> void {
std::cout << c << " ";
}
auto print(float const& b) -> void {
std::cout << b << " ";
}
auto print(float b, const char* c) -> void {
print(b);
print(c);
}
auto print(int const& a) -> void {
std::cout << a << " ";
}
auto print(int a, float b, const char* c) -> void {
print(a);
print(b, c);
}
demo808-variadic.cpp
#include <iostream>
#include <typeinfo>
template <typename A, typename... B>
auto print(A head, B... tail) -> void {
std::cout<<head;
print(tail...);
}
auto main() -> int {
print(1, 2.0f);
std::cout << "\n";
print(1, 2.0f, "Hello");
std::cout << "\n";
}
#include <iostream>
#include <typeinfo>
template <typename A, typename... B>
auto print(A head, B... tail) -> void {
auto i=sizeof...(tail);
std::cout<<i<<std::endl;
std::cout<<head;
print(tail...);
}
auto main() -> int {
print(1, 2.0f);
std::cout << "\n";
print(1, 2.0f, "Hello");
std::cout << "\n";
}
#include <vector>
template <typename T>
class stack {
public:
auto push(T& t) -> void { stack._push_back(t); }
auto top() -> T& { return stack_.back(); }
private:
std::vector<T> stack_;
};
auto main() -> int {
auto is1 = stack<int>{};
is1.push(2);
is1.push(3);
auto is2 = stack<int>{is1}; // this works
auto ds1 =
stack<double>{is1}; // this does not
}
Through use of member templates, we can extend capabilities
#include <vector>
template <typename T>
class stack {
public:
explicit stack() {}
template <typename T2>
stack(stack<T2>&);
auto push(T t) -> void { stack_.push_back(t); }
auto pop() -> T;
auto empty() const -> bool { return stack_.empty(); }
private:
std::vector<T> stack_;
};
template <typename T>
T stack<T>::pop() {
T t = stack_.back();
stack_.pop_back();
return t;
}
template <typename T>
template <typename T2>
stack<T>::stack(stack<T2>& s) {
while (!s.empty()) {
stack_.push_back(static_cast<T>(s.pop()));
}
}
auto main() -> int {
auto is1 = stack<int>{};
is1.push(2);
is1.push(3);
auto is2 = stack<int>{is1}; // this works
auto ds1 =
stack<double>{is1}; // this does not work
// until we do the changes on the left
}
demo809-membertemp.cpp
template <typename T, template <typename> typename CONT>
class stack {}
#include <iostream>
#include <vector>
auto main(void) -> int {
stack<int, std::vector<int>> s1;
s1.push(1);
s1.push(2);
std::cout << "s1: " << s1 << "\n";
stack<float, std::vector<float>> s2;
s2.push(1.1);
s2.push(2.2);
std::cout << "s2: " << s2 << "\n";
//stack<float, std::vector<int>> s2; :O
}
Ideally we can just do:
#include <iostream>
#include <vector>
auto main(void) -> int {
stack<int, std::vector> s1;
s1.push(1);
s1.push(2);
std::cout << "s1: " << s1 << std::endl;
stack<float, std::vector> s2;
s2.push(1.1);
s2.push(2.2);
std::cout << "s2: " << s2 << std::endl;
}
#include <iostream>
#include <vector>
template <typename T, typename Cont>
class stack {
public:
auto push(T t) -> void { stack_.push_back(t); }
auto pop() -> void { stack_.pop_back(); }
auto top() -> T& { return stack_.back(); }
auto empty() const -> bool { return stack_.empty(); }
private:
CONT stack_;
};
#include <iostream>
#include <vector>
#include <memory>
template <typename T, template <typename...> typename CONT>
class stack {
public:
auto push(T t) -> void { stack_.push_back(t); }
auto pop() -> void { stack_.pop_back(); }
auto top() -> T& { return stack_.back(); }
auto empty() const -> bool { return stack_.empty(); }
private:
CONT<T> stack_;
};
#include <iostream>
#include <vector>
auto main(void) -> int {
auto s1 = stack<int, std::vector>{};
s1.push(1);
s1.push(2);
}
auto main(void) -> int {
stack<int, std::vector<int>> s1;
int i1 = 1;
int i2 = 2;
s1.push(i1);
s1.push(i2);
while (!s1.empty()) {
std::cout << s1.top() << " ";
s1.pop();
}
std::cout << "\n";
}
demo810-temptemp.cpp
#include <vector>
#include <iostream>
template <class T, template <class...> class C, class U>
C<T> All_V(const C<U> &c) {
C<T> result(c.begin(), c.end());
return result;
}
int main() {
std::vector<float> vf = {1.2, 2.6, 3.7};
auto vi = All_V<int>(vf);
for(auto &&i: vi) {
std::cout << i << std::endl;
}
}
The compiler considers the partial specializations based on a template template argument once you have instantiated a specialization based on the corresponding template template parameter.
Template Argument Deduction is the process of determining the types (of type parameters) and the values of nontype parameters from the types of function arguments.
template <typename T, int size>
T findmin(const T (&a)[size]) {
T min = a[0];
for (int i = 1; i < size; i++) {
if (a[i] < min) min = a[i];
}
return min;
}
type paremeter
non-type parameter [nullptr, integral values, lvalue references, pointer, enumerations, and floating-point values]
call parameters
template <auto N> // (1)
class MyClass{
....
};
template <int N> // (2)
class MyClass<N> {
....
};
MyClass<'x'> myClass1; // (3)
MyClass<2017> myClass2; // (4)
Automatic type deduction for non-type templates can also be applied to variadic templates
template <auto... ns>
class VariadicTemplate{ .... };
template <auto n1, decltype(n1)... ns>
class TypedVariadicTemplate{ .... };
std::vector<int> v{1,2,3};
std::vector v{1,2,3};
// array to pointer
template <typename T>
f(T* array) {}
int a[] = { 1, 2 };
f(a);
// const qualification
template <typename T>
f(const T item) {}
int a = 5;
f(a); // int => const int;
// conversion to base class
// from derived class
template <typename T>
void f(base<T> &a) {}
template <typename T>
class derived : public base<T> { }
derived<int> d;
f(d);
f(1,2); // f<int>(1,2);
f(1.1,2.2); f<double>(1.1,2.2);
f('a', 'b'); f<char>(char a, char b);
f(2, 'b'); ?
f(2, 2.2); ? ?
//
template<typename T>
T f(T a, T b)
return a+b;
template<typename T1, typename T2>
T f(T1 a, T2 b)
return a+b;
If we need more control over the normal deduction process, we can explicitly specify the types being passed in
template<typename T>
auto min(T1 a, T2 b) {
return a < b ? a : b;
}
auto main() -> int {
auto i = int{0};
auto d = double{0};
min(i, static_cast<int>(d)); // int min(int, int)
// min<int>(i, d); // int min(int, int)
min(static_cast<double>(i), d); // double min(double, double)
min<double>(i, d); // double min(double, double)
}
demo811-explicitdeduc.cpp