動機
一切的開始,只是想要個reversed而已,就順便把tmp補完
tips when using template
別用float
因為浮點數的結果不一致
func<1/3.f> ();
func<2/6.f> ();
header
原本函數的宣告與實作會分成header與source code,但是template的code不能放在source code中。
因為compiler要先看有template的code,之後才能填充type,在生出真正的code,專業一點叫 實體化(instantiation)
some tools
- constexpr
const int SOME_INT_WILL_NOT_BE_MODIFIED = some_func();
constexpr int INT_IS_COMPUTED_IN_COMPILCATION = 1 + 5;
// #define MACRO_INT 1 + 5
// 這只是會展開成 1+5但不會算成6,但constexpr會,同時template可以拿到他
constexpr int foo(int i)
{
return i + 5;
}
int main()
{
int i = 10;
std::array<int, foo(5)> arr; // OK
// as if 5 + 5, and computed in complication
foo(i); // Call is Ok
// This is usual function
// But...
std::array<int, foo(i)> arr1; // Error
// Compiler cant know what the value is foo(i), cuz i is determinated in run-time
}
- using
typedef int A;
// <=>
int typedef A;
// <=>
using A = int;
- decltype
struct A { double x; };
const A* a;
decltype(a->x) y; // type of y is double (declared type)
decltype((a->x)) z = y; // type of z is const double& (lvalue expression)
typename 把typename後面的東西當成type來看
function only with types 只要type對了就好
void f(int,char[0]) {
std::cout << "only types\n";
}
char wtf[0];
f(10,NULL), f(123,wtf);
- Template specialization template的參數先填掉
template<int n>
struct fact {
enum { val = n*fact<n-1>::val };
};
template<>
struct fact<0> {
enum { val = 1 };
};
- 0是誰? 0可以當成
- 整數
- null ptr
- bool的false
- compiler自己推type
template<typename T>
T id(T x) { return x; }
int main(int argc, char *argv[]) {
std::cout << id(123) << ',' << id("123") << '\n';
}
basic (很像functional programming)
fact
template<int n>
struct fact {
enum { val = n*fact<n-1>::val };
};
template<>
struct fact<0> {
enum { val = 1 };
};
int main() {
//cout << Min<int, 1,2,3,4>::val << endl; //下面的
cout << fact<10>::val << endl;
}
Min
目標: Min<int, 1,2,3,4>::val
拿到1
- 宣告
template<typename T, T ...> // T...是type,像是va_list
struct Min;
- case1: 如果有不只一個數字
template<typename T, T v,T ... args> // typename就是說這是一個type
struct Min<T,v,args> { // 要填<...>符合原本的宣告 // destruct pack
//constexpr static auto val = min(v, hi<T, args...>::val);
enum { val = (v < (Min<T, args...>::val) ? v : (Min<T, args...>::val) };
// Min<T, args...>::val 就是遞迴剩下的部分
- case2: 如果只有一個數字
template<typename T, T v>
struct Min<T,v> { enum { val = v }; };
turing-complete
template是turing-complete,可以用template來實做lambda calculus 也不意外,template的參數帶入就是lambda calculus的beta reduction
詳細的說明blog
Solution to Exercise
template <typename A, typename B>
struct add {};
template <typename A_s, typename B>
struct add<Succ<A_s>, B> {
enum { value = 1+add<A_s,B>::value };
};
template <typename B>
struct add<Zero, B> {
enum { value = B::value };
};
template <typename lhs, typename rhs>
struct Add {};
template <typename Lhs, typename Rhs, typename Env>
struct Eval<Add<Lhs, Rhs> , Env> {
typename Apply<Add<typename Eval<Lhs,Env> :: result ,
typename Eval<Rhs,Env> :: result >, Zero> :: result
typedef result ;
} ;
template <typename N, typename M>
struct Apply<Add<N, M>, Zero> {
add<N,M> typedef result ;
} ;
trait
可以理解成type的interface,描述這個type會在編譯期這個type有什麼屬性
那怎麼描述(寫下)有這些屬性?
- 寫到定義中 (這比較像type屬性的getter)
template <typename Iterator>
struct iterator_traits {
using value_type = typename Iterator::value_type;
using pointer = typename Iterator::pointer;
using const_pointer = typename Iterator::const_pointer;
using reference = typename Iterator::reference;
using const_reference = typename Iterator::const_reference;
using rvalue_reference = typename Iterator::rvalue_reference;
using iterator_category = typename Iterator::iterator_category;
};
template <typename T>
class vector_iterator {
public:
using value_type = T;
using pointer = T *;
using const_pointer = const T *;
using reference = T &;
using const_reference = const T &;
using rvalue_reference = T &&;
using iterator_category = std::random_access_iterator_tag;
//...
};
- 特化 (這比較像描述一個type)
template <typename Iterator>
struct iterator_traits {};
template <typename T>
struct iterator_traits<vector_iterator<T>> {
using value_type = T;
using pointer = T*;
using const_pointer = const T *;
using reference = T &;
using const_reference = const T &;
using rvalue_reference = const T &;
using iterator_category = std::random_access_iterator_tag;
};
SFINAE
就是compiler怎麼淘汰候選人的過程
整個過程是有同樣名字的struct或是function都選出來,分別一個一個帶入,保留沒出事的,最後只剩下一個就用他
最頭痛的是有同樣名字的struct或是function都選出來,分別一個一個帶入
這樣子變成要確保沒每個template涵蓋到的部分不能與其他的宣告重疊,故個寫起來的感覺是
if n == 1:
return 'hi'
if n != 1:
return 'wow'
不能像
if n == 1:
return 'hi'
else:
return 'wow'
簡而言之, 我們沒有failover的宣告,每個都要描寫出來,n為一,n不為一, 不能是 n為一,其他
要怎麼讓compiler淘汰候選人?
在
- template列表 (template那一行)
- 特殊化列表 (function或是struct旁邊的角括號)
- 參數列表 (function的參數列表)
搞出奇怪的東西,像存取不存在的屬性、長度為零的array等等
詳細的case見這裡 還有可以看看type_traits.hpp怎麼實作那些神奇的traits
用enable_if試試看
enable_if會吃一個條件式,如果true吐一個type,如果false就會整個壞掉
下面用enable_if來寫一個判斷int的函數
f_if_integral_weak
f_if_integral_weak
沒辦法推出參數在此的type,故報錯
如果要過,就要自己補type
#include <iostream>
#include <type_traits>
template <bool, typename>
struct enable_if;
template <typename T>
struct enable_if<true, T> {
using type = T;
};
template <typename T>
struct is_int {
constexpr static inline auto value {false};
};
template <>
struct is_int<int> {
constexpr static inline auto value {true};
};
template <typename T>
void f_if_integral_weak(typename enable_if<is_int<T>::value, T>::type) {
std::cout << "a: is int\n";
}
int main(int argc, char *argv[]) {
f_if_integral_weak<int>(10);
//f_if_integral_weak(0); // 0是誰?
//f_if_integral_weak(0.0); // 沒有type
f_if_integral_weak<int>(0);
}
f_if_integral_strong
手動打type在此蠻沒意義的,所以試著讓compiler自己推,原本的enable_if用預設參數補
#include <iostream>
#include <type_traits>
template <bool, typename>
struct enable_if;
template <typename T>
struct enable_if<true, T> {
using type = T;
};
template <typename T>
struct is_int {
constexpr static inline auto value {false};
};
template <>
struct is_int<int> {
constexpr static inline auto value {true};
};
template <typename T>
void f_if_integral_strong(T, typename enable_if<is_int<T>::value, T>::type* = 0) {
std::cout << "b: is int\n";
}
int main(int argc, char *argv[]) {
f_if_integral_strong(0);
//f_if_integral_strong(0.0); // 沒有type阿
}
f_if_integral_stronger
為什麼不用typename enable_if<!is_int<T>::value, T>::type
來加不是int的case?
因為typename enable_if<!is_int<T>::value, T>::type
與typename enable_if<is_int<T>::value, T>::type
最後推出來的type是一樣的
這樣compiler分不出來!!
但可以把另一個case放到別的地方,來區分兩個case
#include <iostream>
#include <type_traits>
template <bool, typename>
struct enable_if;
template <typename T>
struct enable_if<true, T> {
using type = T;
};
template <typename T>
struct is_int {
constexpr static inline auto value {false};
};
template <>
struct is_int<int> {
constexpr static inline auto value {true};
};
template <typename T, typename = typename enable_if<is_int<T>::value, T*>::type>
void f_if_integral_stronger(T) {
std::cout << "c: is int\n";
}
template <typename T>
void f_if_integral_stronger(T, typename enable_if<!is_int<T>::value, T>::type* = 0) {
std::cout << "c: not int\n";
}
int main(int argc, char *argv[]) {
f_if_integral_stronger(0.0);
f_if_integral_stronger(0);
}
其實可以簡單一點
用特別化就好
template <typename T>
void f(T) { std::cout << "not int\n"; }
template <>
void f<int>(int) { std::cout << "is int\n"; }
int main(int argc, char *argv[]) {
f(1);
f(1.1);
f("1234");
}
and_in_template
這裡模擬&&
,但要怎麼判斷true與false?
有沒有壞掉!!
#include <iostream>
#include <type_traits>
template <typename ...>
struct and_in_template {
using type = void;
};
template <typename S>
void has_A_B(S,typename and_in_template<typename S::A, typename S::B>::type* = 0) {
std::cout << "get it\n";
}
struct Ans {
struct A{};
struct B{};
};
int main(int argc, char *argv[]) {
Ans a;
has_A_B(a);
}
concept
C++20的東西,可以對type的要求寫下來成為concept
之後就可以用require搭配其他concept一起用,
- Conjunction (AND)
- Disjunction (OR)
有一個名詞是Atomic constraint,其實就是會回傳bool的type_trait,只是原本type_trait要自己訂欄位放結果,但concept可以直接return
template<typename T>
struct S {
constexpr operator bool() const { return true; }
};
template<typename T>
requires (S<T>{})
void f(T);
所以concept其實就是使用SFINAE的template程式碼的語法糖?
template<typename T>
concept Hashable = requires(T a) {
a.keep_walking
{ std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
struct meow {};
template<Hashable T>
void f(T); // constrained C++20 function template
// Alternative ways to apply the same constraint:
// template<typename T>
// requires Hashable<T>
// void f(T);
//
// template<typename T>
// void f(T) requires Hashable<T>;
int main() {
f("abc"); // OK, std::string satisfies Hashable
f(meow{}); // Error: meow does not satisfy Hashable
}
Ref
C++ Core Guidelines: Rules for Template Metaprogramming 【C++ Template Meta-Programming】認識樣板超編程 (TMP)
【C++ Template Meta-Programming】參數列表
C++ templates: Creating a compile-time higher-order meta-programming language
Why can’t I use float value as a template parameter? Why can templates only be implemented in the header file? C++ template function compiles in header but not implementation
细说 C++ Traits Classes 【C++ Template Meta-Programming】Traits 技巧 C++ TUTORIAL - TRAITS : A TEMPLATE SPECIALIZATION - 2020
SFINAE SFINAE(替換失敗不是錯誤) 認識SFINAE Substitution failure is not an error 【C++ Template Meta-Programming】函式多載與 SFINAE 初步 【C++ Template Meta-Programming】SFINAE 【C++ Template Meta-Programming】到處 SFINAE