概述
模板元編程可以說是C++中最困難也是最強大的編程范式。模版元編程不同於普通的運行期程序,它執行完全是在編譯期,並且它操縱的數據不能是運行時變量,只能是編譯期常量(且不可修改)。因此,模版元編程需要很多技巧,在這里你會遇到非常多的枚舉常量、繼承、模板偏特化、可變模板參數、類型萃取等方法,如果要讀懂STL庫,那么模板元編程必須要掌握;不僅如此,C++中的許多黑科技都是依賴於模板元編程的,例如我們甚至可以寫出編譯期排序的算法
模板元編程:template meta programing
模板元編程又兩個部分組成:元數據和元函數。元數據是指編譯期能在編譯期處理的數據,即編譯期常量;元函數指操縱元數據的函數,在編譯期調用,它通常表現為一個模板類或一個模板函數
模板元編程十分特殊,它無法使用if-else
,for
,while
這些運行期語句。但是我們可以通過std::enable_if
,std::integer_sequence
等元函數來實現各種操作
type_traits-定義元數據
<type_traits>
是C++11提供的模板元基礎庫,它提供了模板元編程中需要的常用的基礎元函數
std::integral_constant,定義編譯期常量
舉個例子,C++11中提供了std::integral_constant
來定義編譯期常量
template<typename T>
using one_constant = std::integral_constant<T, 1>;
template<typename T>
struct one_struct : std::integral_constant<T, 1> {};
因此我們可以使用one_constant<int>::value
或one_struct<int>::value
來獲取編譯期常量int 1
而在C++11之前,我們定義這個常量就需要用到enum
或static-const
struct one_struct
{
enum { value = 1 };
};
struct one_struct
{
static const int value = 1;
};
然后通過one_struct::value
來訪問值,其中enum
能隱式轉換為int
std::integral_constant
的實現也非常的簡單
template <class _Ty, _Ty _Val>
struct integral_constant {
static constexpr _Ty value = _Val;
using value_type = _Ty;
using type = integral_constant;
constexpr operator value_type() const noexcept {
return value;
}
_NODISCARD constexpr value_type operator()() const noexcept {
return value;
}
};
可以看到,通過C++11的<type_traits>
提供的一個簡單的std::integral_constant
就可以很方便的定義編譯期常量,而無需再去使用enum
和static-const。緊接着,庫中又提供了編譯期常量的bool
類型
template <bool _Val>
using bool_constant = integral_constant<bool, _Val>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
std::integer_sequence
std::is_integral_v,判斷是否為整形
std::is_integral_v
,根據名稱可以看出它可以用於判斷T
是否為integral類型,從_v
可以得出它是一個元數據。std::is_integral_v
在對T去除cv限定符后,在一堆integral
類型中進行匹配比較,最后萃取出一個bool
編譯期常量
// STRUCT TEMPLATE is_integral
template <class _Ty>
inline constexpr bool is_integral_v = _Is_any_of_v<remove_cv_t<_Ty>, bool, char, signed char, unsigned char,
wchar_t,
#ifdef __cpp_char8_t
char8_t,
#endif // __cpp_char8_t
char16_t, char32_t, short, unsigned short, int, unsigned int, long, unsigned long, long long, unsigned long long>;
那么僅僅有一個“值”是不夠的,我們還需要一個類型,那就是std::is_integral
,這里從“值”到“類型”的“逆”轉換很巧妙
為什么說是“逆”轉換呢,因為這個類的聲明順序是現有
std::is_integral_v
,后有std::is_integral
template <class _Ty>
struct is_integral : bool_constant<is_integral_v<_Ty>> {};
// true 兩者都派生自std::bool_constant<false>
std::cout << std::boolalpha << std::is_base_of_v<std::false_type, std::is_integral<std::string>> << std::endl;
// true 因為long long是整形類型
std::cout << std::boolalpha << std::is_integral_v<long long> << std::endl;
std::integer_sequence,定義編譯期整數序列
為什么說是整形呢,請看源碼。如果不是整形類型那么IDE和編譯期會給出靜態斷言報錯
// 一個類型加上一個非類型模板參數包
template <class _Ty, _Ty... _Vals>
struct integer_sequence { // sequence of integer parameters
static_assert(is_integral_v<_Ty>, "integer_sequence<T, I...> requires T to be an integral type.");
using value_type = _Ty;
static constexpr size_t size() noexcept {
return sizeof...(_Vals);
}
};
來通過一個簡單的例子來了解它
template<typename T, T... Is>
void print_sequence(std::integer_sequence<T, Is...> sequence)
{
((std::cout << sequence.size() << "elements: ") << ... << Is);
}
int main()
{
// 6 elements: 4328910
print_sequence(std::integer_sequence<unsigned int, 4, 3, 2, 8, 9, 10>());
// 10 elements: 0123456789
print_sequence(std::make_integer_sequence<int, 10>());
// 10 elements: 0123456789
print_sequence(std::make_index_sequence<10>());
}
其他的相關源碼為
// 構建一個std::integer_sequence
// ALIAS TEMPLATE make_integer_sequence
template <class _Ty, _Ty _Size>
using make_integer_sequence = __make_integer_seq<integer_sequence, _Ty, _Size>;
template <size_t... _Vals>
using index_sequence = integer_sequence<size_t, _Vals...>;
// 構建一個std::integer_sequence<std::size_t>
template <size_t _Size>
using make_index_sequence = make_integer_sequence<size_t, _Size>;
template <class... _Types>
using index_sequence_for = make_index_sequence<sizeof...(_Types)>;
-
std::make_integer_sequence
能創建一個0-_Size - 1
的序列,需要注意的是,它並不是一個可調用的函數,而是一個type alias(模板別名) -
std::index_sequence
是std::size_t
類型的std::integer_sequence
-
std::index_sequence_for
將以參數包的個數作為序列的_Size
,創建一個0-_Size - 1
的序列// 5 elements: 01234 print_sequence(std::index_sequence_for<float, std::iostream, char, double, std::shared_ptr<std::string>>{}); // 參數包大小為5
利用編譯期序列實現數組到元組的轉換
這是一個cppreference上的例子
template<typename Array, std::size_t... Is>
auto array_to_tuple_impl(const Array& _array, std::index_sequence<Is...> _sequence)
{
return std::make_tuple(_array[Is]...);
}
template<typename T, std::size_t I, typename Sequence = std::make_index_sequence<I>>
auto array_to_tuple(const std::array<T, I> _array)
{
return array_to_tuple_impl(_array, Sequence{});
}
int main()
{
std::array<int, 3> a{12, 3, 2};
auto full_t = array_to_tuple(a);
auto part_t = array_to_tuple<int, 3, std::index_sequence<1, 2>>(a);
// 32
std::apply([](auto&&... obj) { ((std::cout << obj << " "), ...); }, full_t);
}
但是這個輸出std::tuple
的方法並不完美,它會在輸出的末尾添加一個不必要的" "
,所以在此之上進行一層編譯期序列的封裝,能夠判斷此時解析到元組的第幾個元素
template<typename Tuple, std::size_t... Is>
std::ostream& print_tuple_impl(std::ostream& os, const Tuple& tuple, std::index_sequence<Is...> sequence)
{
return ((os << (Is == 0 ? "" : " ") << std::get<Is>(tuple)), ...);
}
template<typename... Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tuple)
{
return print_tuple_impl(os, tuple, std::index_sequence_for<Ts...>{});
// return print_tuple_impl(os, tuple, std::make_index_sequence<sizeof...(Ts)>{});
}
// perfect print
std::cout << full_t << std::endl; // 12 3 2
type_traits-判斷元數據
std::is_array,判斷是否數組類型
源碼十分簡單,根據特化類型來判斷數組類型
// TYPE PREDICATES
template <class>
inline constexpr bool is_array_v = false; // determine whether type argument is an array
template <class _Ty, size_t _Nx>
inline constexpr bool is_array_v<_Ty[_Nx]> = true;
template <class _Ty>
inline constexpr bool is_array_v<_Ty[]> = true;
// STRUCT TEMPLATE is_array
template <class _Ty>
struct is_array : bool_constant<is_array_v<_Ty>> {};
測試代碼
float arr[10]{};
// true
std::cout << std::boolalpha << std::is_array_v<decltype(arr)> << std::endl;
// C++20 arr是一個有邊界的數組 故輸出false
std::cout << std::boolalpha << std::is_unbounded_array_v<decltype(arr)> << std::endl;
// false
std::cout << std::boolalpha << std::is_array_v<std::array<int, 10>> << std::endl;
std::is_same,判斷兩種類型是否相同
template <class, class>
inline constexpr bool is_same_v = false; // determine whether arguments are the same type
template <class _Ty>
inline constexpr bool is_same_v<_Ty, _Ty> = true;
template <class _Ty1, class _Ty2>
struct is_same : bool_constant<is_same_v<_Ty1, _Ty2>> {};
測試代碼
// 一般情況int都占4B true
std::cout << std::boolalpha << std::is_same_v<int, std::int32_t> << std::endl;
// true
std::cout << std::boolalpha << std::is_same_v<int, signed int> << std::endl;
更多類型判斷

更多內容可以查看文檔:cppreference-類型屬性
type_traits-類型修改
根據上文中的std::is_array
,我們可以看到原理是對不同的類型參數做出不同的特化。現在我們還可以利用特化做點別的事情
std::remove_const,移除類型的const屬性
// STRUCT TEMPLATE remove_const
template <class _Ty>
struct remove_const { // remove top-level const qualifier
using type = _Ty;
};
template <class _Ty>
struct remove_const<const _Ty> {
using type = _Ty;
};
template <class _Ty>
using remove_const_t = typename remove_const<_Ty>::type;
測試代碼為
std::remove_const_t<const int> data = 100;
data = 10;
std::decay,朽化
std::is_const,判斷此類型是否是const限定的
std::is_const
的實現是
// STRUCT TEMPLATE is_const
template <class>
inline constexpr bool is_const_v = false; // determine whether type argument is const qualified
template <class _Ty>
inline constexpr bool is_const_v<const _Ty> = true;
// true
std::cout << std::boolalpha << std::is_const_v<const int> << std::endl;
// false
std::cout << std::boolalpha << std::is_const_v<const int&> << std::endl;
// false
std::cout << std::boolalpha << std::is_const_v<const int(std::string, double)> << std::endl;
// false 因為是底層指針 指針本身是可以被修改的
std::cout << std::boolalpha << std::is_const_v<const int*> << std::endl;
// true 因為是頂層指針 指向的指向不能更改
std::cout << std::boolalpha << std::is_const_v<int* const> << std::endl;
這一句話很重要
determine whether type argument is const qualified
以const int&
為例,在調用到is_const_v<T>
時,T被推導為int
,const int
和const int&
不符,因此調用不到true
版本的特化,所以結果為false
因此當一個類型的const修飾無意義並且它不是引用類型的時候,它是函數類型,如<int(std::string, double)>
std::is_function,判斷是否為函數類型
// STRUCT TEMPLATE is_function
template <class _Ty>
inline constexpr bool is_function_v = // only function types and reference types can't be const qualified
!is_const_v<const _Ty> && !is_reference_v<_Ty>;
template <class _Ty>
struct is_function : bool_constant<is_function_v<_Ty>> {};
這一句話很重要
only function types and reference types can't be const qualified
std::remove_extent,移除一層數組
如果是一維數組,那么獲取它的元素類型;如果是高維數組,那么移除第一層數組
// true
std::cout << std::boolalpha << std::is_same_v<std::remove_extent_t<const int[]>, const int> << std::endl;
// true
std::cout << std::boolalpha << std::is_same_v<std::remove_extent_t<int[][10]>, int[10]><< std::endl;
朽化的具體步驟
對於最終的結果type
,std::decay
做了很多工作
// STRUCT TEMPLATE decay
template <class _Ty>
struct decay { // determines decayed version of _Ty
using _Ty1 = remove_reference_t<_Ty>;
using _Ty2 = typename _Select<is_function_v<_Ty1>>::template _Apply<add_pointer<_Ty1>, remove_cv<_Ty1>>;
using type = typename _Select<is_array_v<_Ty1>>::template _Apply<add_pointer<remove_extent_t<_Ty1>>, _Ty2>::type;
};
template <class _Ty>
using decay_t = typename decay<_Ty>::type;
- 首先移除類型的引用類型,記為
_Ty1
- 如果
_Ty1
是函數類型,那么在_Ty1
的基礎上加一層指針,記為_Ty2
,否則_Ty2
為移除cv限定符的_Ty1
- 如果
_Ty1
是數組類型,那么記type
為移除一層數組且加上一層指針的_Ty1
,否則記type
為_Ty2
例子
std::decay_t<int> A; // int
std::decay_t<int&> B; // int
std::decay_t<int&&> C; // int
std::decay_t<const int&> D; // int
std::decay_t<int[10]> E; // int*
std::decay_t<int(int, int)> F; // int(*)(int, int)
std::_Add_reference,添加引用類型
這個元函數也有必要挑出來講一下,實現也是非常的巧妙
// STRUCT TEMPLATE _Add_reference
template <class _Ty, class = void>
struct _Add_reference { // add reference (non-referenceable type)
using _Lvalue = _Ty;
using _Rvalue = _Ty;
};
template <class _Ty>
struct _Add_reference<_Ty, void_t<_Ty&>> { // (referenceable type)
using _Lvalue = _Ty&;
using _Rvalue = _Ty&&;
};
重點是這個void_t<_Ty&>
。首先。void
類型是無法添加左值引用或右值引用的,如void&
和void&&
都會導致編譯錯誤。因此void_t<_Ty&>
其實實現了一個substitution failure,對於_Add_reference<void>
來說,並不會導致報錯,再“添加”了引用后void
仍然是void
The major difference to directly using
T&
is thatstd::add_lvalue_reference<void\>::type
is void, whilevoid&
leads to a compilation error.
更多類型修改

更多內容可以查看文檔:cppreference-類型屬性
編譯期判斷
上文中提到過,模板元編程中沒有if-else
這些判斷語句,但是<type_traits>
中提供了不少元函數來實現這些功能
std::conditional,三目運算符
源碼實現也非常的簡單,都是技巧
// STRUCT TEMPLATE conditional
template <bool _Test, class _Ty1, class _Ty2>
struct conditional { // Choose _Ty1 if _Test is true, and _Ty2 otherwise
using type = _Ty1;
};
template <class _Ty1, class _Ty2>
struct conditional<false, _Ty1, _Ty2> {
using type = _Ty2;
};
template <bool _Test, class _Ty1, class _Ty2>
using conditional_t = typename conditional<_Test, _Ty1, _Ty2>::type;
// 是不是感覺很眼熟
int a = (100 > 50) ? 100 : 50;
using IntType = std::conditional_t<true, int, float>;
using FloatType = std::conditional_t<false, int, float>;
注意,該原函數計算的參數和結果都是類型
std::enable_if,if-true表達式
源碼也是非常的簡單
// STRUCT TEMPLATE enable_if
template <bool _Test, class _Ty = void>
struct enable_if {}; // no member "type" when !_Test
template <class _Ty>
struct enable_if<true, _Ty> { // type is _Ty for _Test
using type = _Ty;
};
template <bool _Test, class _Ty = void>
using enable_if_t = typename enable_if<_Test, _Ty>::type;
它只實現了true
時期的特化,而當條件為false
時,std::enable_if
為空類,試圖調用不存在的type
將會導致編譯出錯
限制模板參數的參數類型
在C#中存在一個where
關鍵字可以用來限制泛型的類型,C++中同樣可以實現這一點
在小學數學中,我們稱能被2整除的整數為偶數,聰明的讀者可能會認為應該這么寫
// 只有整形才有%運算符
template<typename T>
bool is_odd(std::enable_if_t<std::is_integral_v<T>, T> num)
{
return num % 2 == 0;
}
但是這樣會導致一個問題,函數模板沒有辦法自動推導參數的類型
std::cout << std::boolalpha << is_odd<int>(20) << std::endl;
所以我們應該把類型判斷放到返回值中
template<typename T>
std::enable_if_t<std::is_integral_v<T>, bool> is_odd(T num)
{
return num % 2 == 0;
}
std::cout << std::boolalpha << is_odd(20) << std::endl;
// std::cout << std::boolalpha << is_odd(20.0) << std::endl; // 編譯出錯
或者把它當作模板函數的匿名默認參數
template<typename T>
bool is_odd(T data, std::enable_if_t<std::is_integral_v<T>, void>* = nullptr)
{
return data % 2 == 0;
}
匹配函數重載或偏特化類
SFINAE
SFINAE:Substitution failure is not an error,替代失敗不代表是個錯誤
-
error,在這里指編譯錯誤,當出現編譯錯誤的時候會停止代碼的鏈接等后續活動。如上述例子中判斷偶數的模板函數中,若函數參數非整形類型,那么會出現error
std::cout << std::boolalpha << is_odd(20.0) << std::endl;
-
failure,當失敗發生時不代表編譯出錯,當我們使用
switch
,if-else
或是函數重載時,failure隨處可見,這里以函數重載為例void foo(std::size_t) {} void foo(std::string) {}
foo("Jelly"); foo((int*)nullptr);
當我們執行第一個
foo
時,我們可以認為它對std::size_t
的調用是一個failure,然后成功的調用到了std::string
的版本當我們執行第二個
foo
時,我們可以認為它出現了兩次failure,並且兩個版本的函數都無法調用,這時出現了error,編譯器給出報錯
所以說,當我們遇到failure時,編譯器往往還會嘗試其他解決問題的方法,只有無路可走時才會出現error,這就是failure is not an error
所謂Substitution,就是將函數模板中的形參替換為實參的過程,來看一個例子,照搬自知乎
struct X {
typedef int type1;
};
struct Y {
typedef int type2;
};
// foo1
template<typename T>
void foo(typename T::type1) {}
// foo2
template<typename T>
void foo(typename T::type2) {}
// foo3
template<typename T>
void foo(T) {}
int main()
{
foo<X>(5); // foo1: Succeed, foo1: Failed, foo2: Failed
foo<Y>(10); // foo1: Failed, foo1: Succeed, foo2: Failed
foo<int>(15); // foo1: Failed, foo1: Failed, foo2: Succeed
}
很顯然,在調用foo<Y>(10)
時,對於foo1
而言實參明顯無法匹配到形參上,但我們此時並不能武斷的說發生了error,而應該認為發生了substitution failure,然后編譯期會嘗試下一個函數重載
利用SFINAE進行匹配
假設我們實現了一個接口和一個類,其中有increase
的接口
struct ICounter
{
virtual void increase() = 0;
virtual ~ICounter() {}
};
struct Counter : public ICounter
{
virtual void increase() override
{
// code...
}
};
那么現在我想有一個函數,我希望它能夠同時處理整形類型和Counter
的累加操作
template<typename T>
void increase_fuc(T& counter, std::enable_if_t<std::is_base_of_v<ICounter, T>, void>* = nullptr)
{
counter.increase();
}
template<typename T>
void increase_fuc(T& num, std::enable_if_t<std::is_integral_v<T>, void>* = nullptr)
{
++num;
}
int data = 0;
Counter c;
increase_fuc(data);
increase_fuc(c);
利用Expression SFINAE進行匹配
你可能覺得上文中使用虛函數會有點消耗性能,又或者你不想使用接口
struct Counter
{
void increase()
{
// code...
}
};
C++11支持Expression SFINAE,讓我們可以不依賴於std::enable_if
寫出函數重載,而使用decltype
template<typename T>
void increase_fuc(T& counter, std::decay_t<decltype(counter.increase())>* = nullptr)
{
// 注意這里的decltype僅起到推斷作用 並不會真正執行
counter.increase();
}
template<typename T>
void increase_fuc(T& num, std::decay_t<decltype(++num)>* = nullptr)
{
// 只有支持++操作的類型才能調用到這個重載
++num;
}
由於int&*
是非法的,因此需要加朽化修飾
// 那么對於int類型而言 在調用第一個重載時就發生了Expression Substitution Failure
int data = 0;
increase_fuc(data);
或者,你還可以這么寫來避免默認參數的使用,雖然說std::is_integral_v<T>
的效果和 decltype(++)
不同,非整形類型的也可能支持++操作,這里僅僅是給出個參考
template<typename T>
std::enable_if_t<std::is_integral_v<T>, void> increase_fuc(T& num)
{
// 只有支持++操作的類型才能調用到這個重載
++num;
}
template<typename T>
auto increase_fuc(T& num) -> decltype(void(++num))
{
// 只有支持++操作的類型才能調用到這個重載
++num;
}
使用C++17中的if-constexpr
template<typename T>
void increase_fuc(T& counter)
{
if constexpr(std::is_base_of_v<ICounter, T>)
counter.increase();
else if (std::is_integral_v<T>)
++counter;
}
手撕環節
實現函數類型萃取類
struct PlaceHolderTag {};
struct FunctorTag {};
struct BuildInTypeTag {};
template<typename T, typename Holder = PlaceHolderTag>
struct function_traits;
template<typename T, typename... Ts>
struct function_traits<T(Ts...)> {
using ReturnType = T;
using FunctionType = T(Ts...);
using FunctionPointer = T(*)(Ts...);
template<std::size_t I>
struct args
{
static_assert(I < sizeof...(Ts), "args index out of range");
using ParamType = std::tuple_element_t<I, std::tuple<Ts...>>;
};
template<std::size_t I>
using args_t = typename args<I>::ParamType;
};
// std::function類型
template<typename T, typename... Ts>
struct function_traits<std::function<T(Ts...)>> : public function_traits<T(Ts...)> {};
// 函數指針
template<typename T, typename... Ts>
struct function_traits<T(*)(Ts...)> : public function_traits<T(Ts...)> {};
// 成員函數
template<typename ClassType, typename T, typename...Ts>
struct function_traits<T (ClassType::*)(Ts...)> : public function_traits<T(Ts...)> {};
// 通過標簽分發內置類型與仿函數
template<typename T, typename Holder>
struct function_traits : public function_traits<T, std::conditional_t<std::is_class_v<T>, FunctorTag, BuildInTypeTag>> {};
// 內置類型
template<typename BuildInType>
struct function_traits<BuildInType, BuildInTypeTag>
{
static_assert(std::_Always_false<BuildInType>, "this is just a build in type");
};
// 仿函數
template<typename ClassType>
struct function_traits<ClassType, FunctorTag> : public function_traits<decltype(&ClassType::operator())> {};
#define PRINT_TYPEID(T) std::cout << typeid(T).name() << std::endl
int main()
{
std::function<int(std::string)> f;
// int
PRINT_TYPEID(function_traits<decltype(f)>::ReturnType);
// 編譯出錯 "args index out of range"
PRINT_TYPEID(function_traits<decltype(f)>::args_t<1>);
// 編譯出錯 "this is just a build in type"
PRINT_TYPEID(function_traits<int>);
}
但是對成員函數的重載並沒有這么簡單,對於常成員函數執行萃取操作,會導致編譯錯誤
struct Test_Functor
{
void non_const_func(double) {}
void const_func(int) const {}
};
int main()
{
PRINT_TYPEID(function_traits<decltype(&Test_Functor::non_const_func)>::FunctionType);
// 因為缺少對常函數的特化 所以其實會調用到內置類型的版本
// PRINT_TYPEID(function_traits<decltype(&Test_Functor::const_func)>::ReturnType);
}
所以我們可以使用宏來減去模板的重復書寫,其中宏中的__VA_ARGS__
代表參數包...
// 成員函數
#define MEMBER_FUNCTION_TRAITS(...) \
template<typename ClassType, typename T, typename...Ts> \
struct function_traits<T (ClassType::*)(Ts...) __VA_ARGS__> : public function_traits<T(Ts...)> {}; \
MEMBER_FUNCTION_TRAITS()
MEMBER_FUNCTION_TRAITS(const)
MEMBER_FUNCTION_TRAITS(volatile)
MEMBER_FUNCTION_TRAITS(const volatile)
實現std::variant
在C++17中,文檔中說道
The class template
std::variant
represents a type-safe union
但是std::variant
的大小並不是數據包中的最大值,如果創建同類型的union
,那么大小為8。因為std::variant
自帶內存對齊
std::cout << sizeof(std::variant<int, bool, double>) << std::endl; // 16
當我們想要實現自己的variant
時,首先需要實現三個點
- 在編譯期找出數據包中的最大值,以讓運行期能夠分配適量的空間
- 賦值時需要根據原有的類型來調用析構函數,並調用placement new創建新的對象
- 細節項(未實現):元素不能重復,自帶store以及get的類型檢查,檢查類型是否存在於參數包中
編譯期計算,兩個類型的最大者
template<auto Left, auto Right>
inline constexpr bool is_left_greater_than_right = Left > Right;
template<typename Left, typename Right>
struct binary_type_compare_max : std::conditional<is_left_greater_than_right<sizeof(Left), sizeof(Right)>, Left, Right> {};
template<typename Left, typename Right>
using binary_type_compare_max_t = typename binary_type_compare_max<Left, Right>::type;
template<typename T>
inline void print_typeid() { std::cout << typeid(T).name() << std::endl; }
int main()
{
print_typeid<binary_type_compare_max_t<int, double>>();
}
拓展,比較兩個數據的大小並返回較大的那個
template<auto Left, auto Right>
inline constexpr bool is_left_greater_than_right = Left > Right;
template<auto Left, auto Right>
struct binary_size_compare_max : std::conditional<is_left_greater_than_right<Left, Right>,
std::integral_constant<std::size_t, Left>, std::integral_constant<std::size_t, Right>> {};
template<auto Left, auto Right>
inline constexpr auto binary_size_compare_max_v = binary_size_compare_max<Left, Right>::type::value;
int main()
{
std::cout << binary_size_compare_max_v<10, 30> << std::endl;
}
編譯期計算,求一個類型參數包中size的最大者
template<typename... Ts>
struct multi_type_compare_max;
template<typename T, typename U, typename... Ts>
struct multi_type_compare_max<T, U, Ts...> : multi_type_compare_max<binary_type_compare_max_t<T, U>, Ts...> {};
template<typename T>
struct multi_type_compare_max<T> : std::integral_constant<std::size_t, sizeof(T)> {};
template<typename... Ts>
inline constexpr std::size_t multi_type_compare_max_v = multi_type_compare_max<Ts...>::value;
int main()
{
// 最大size為8
std::cout << multi_type_compare_max_v<int, double, std::int64_t, char> << std::endl;
}
編譯期計算,根據index求參數包中的類型
// 根據index獲取type
template<std::size_t index, typename...>
struct my_variant_element;
template<std::size_t index, typename T, typename... Ts>
struct my_variant_element<index, T, Ts...> : my_variant_element<index - 1, Ts...> {
static_assert(index < sizeof...(Ts) + 1, "index out of range");
};
template<typename T, typename... Ts>
struct my_variant_element<0, T, Ts...> {
using type = T;
};
template<std::size_t index, typename... Ts>
using my_variant_element_t = typename my_variant_element<index, Ts...>::type;
template<typename T>
inline void print_typeid() { std::cout << typeid(T).name() << std::endl; }
int main()
{
// char
print_typeid<my_variant_element_t<2, int, double, char, bool>>();
}
編譯期計算,根據參數包中的類型求index
實現一:求差
// 根據type獲取index
template<typename, typename...>
struct my_variant_index;
template<typename Target, typename T, typename... Ts>
struct my_variant_index<Target, T, Ts...> : std::conditional_t<std::is_same_v<Target, T>,
std::integral_constant<std::size_t, sizeof...(Ts)>, my_variant_index<Target, Ts...>> {};
// 匹配失敗
template<typename T>
struct my_variant_index<T> {
static_assert(std::_Always_false<T>, "cant find T in args");
};
template<typename Target, typename... Ts>
inline constexpr std::size_t my_variant_index_v = sizeof...(Ts) - my_variant_index<Target, Ts...>::value - 1;
int main()
{
// int位於參數包的第2位
std::cout << my_variant_index_v<int, char, bool, int, long, long long, float, double> << std::endl;
}
實現二:正向遞增
// 根據type獲取index
template<std::size_t, typename, typename...>
struct my_variant_index_impl;
template<std::size_t index, typename Target, typename T, typename... Ts>
struct my_variant_index_impl<index, Target, T, Ts...> : std::conditional_t<std::is_same_v<Target, T>,
std::integral_constant<std::size_t, index>, my_variant_index_impl<index + 1, Target, Ts...>> {};
// 匹配失敗
template<std::size_t index, typename T>
struct my_variant_index_impl<index, T> {
static_assert(std::_Always_false<T>, "cant find T in args");
};
template<typename Target, typename... Ts>
inline constexpr std::size_t my_variant_index_v = my_variant_index_impl<0, Target, Ts...>::value;
template<typename Target, typename... Ts>
struct my_variant_index : std::integral_constant<std::size_t, my_variant_index_v<Target, Ts...>> {};
實現三:某大佬解法
運行期計算,析構內存上的數據
template<typename... Ts>
struct call_dtor_helper;
template<typename T, typename... Ts>
struct call_dtor_helper<T, Ts...> {
void operator()(std::size_t index, void* memory)
{
if (index != 0)
call_dtor_helper<Ts...>{}(index - 1, memory);
else if (std::is_class_v<T>)
reinterpret_cast<T*>(memory)->~T();
}
};
template<>
struct call_dtor_helper<> {
void operator()(std::size_t, void*) {}
};
my_variant
template<typename... Ts>
class my_variant
{
public:
void* memory;
std::size_t index;
public:
my_variant() : memory(nullptr), index(0) {}
template<typename T>
my_variant(T&& data) : my_variant() {
store(std::forward<T>(data));
}
~my_variant() {
if (memory == nullptr)
return;
call_dtor_helper<Ts...>{}(index, memory);
::operator delete(memory);
}
template<typename T>
void store(T&& data) {
// 無內存對齊
if (memory == nullptr)
memory = ::operator new(multi_type_compare_max_v<Ts...>);
else
call_dtor_helper<Ts...>{}(index, memory);
::new(memory) T(std::forward<T>(data));
index = my_variant_index_v<T, Ts...>;
}
template<typename T>
T& get() {
return *reinterpret_cast<T*>(memory);
}
};
測試代碼
盡管上文中寫的很多編譯期計算的代碼都用不上,但是以學習為目的我並沒有刪除它們。本次手撕環節實現的my_variant功能很弱雞,沒有考慮拷貝以及移動的問題,僅供學習參考
int main()
{
my_variant<long long, NonCopyStruct_Str> v(4ll);
std::cout << v.get<long long>() << std::endl;
v.store<NonCopyStruct_Str>((std::string)"Jelly");
std::cout << v.get<NonCopyStruct_Str>().name << std::endl;
}
更多
std::invoke_result,獲取返回值類型(C++17起)
struct TestResult {
double operator()(char, int&) { return 2.0; }
float operator()(int) { return 1.0; }
};
int main()
{
std::cout << typeid(std::invoke_result_t<TestResult, char, int&>).name() << std::endl; // double
std::cout << typeid(std::invoke_result_t<TestResult, int>).name() << std::endl; // float
std::cout << typeid(decltype(TestResult()(20))).name() << std::endl; // float
}
模板推導類型的舍棄
template<typename T>
struct print_t
{
void print() { std::cout << std::boolalpha << "From T " << std::is_same_v<T, const int&><< std::endl; }
};
template<typename T>
struct print_t<const T>
{
void print() { std::cout << std::boolalpha << "From const T " << std::is_same_v<T, int&><< std::endl; }
};
template<typename T>
struct print_t<T&>
{
void print() { std::cout << std::boolalpha << "From T& " << std::is_same_v<T, const int><< std::endl; }
};
int main()
{
// From T& true
print_t<const int&>().print();
}
如果我們把<T&>
的特化移除,那么
template<typename T>
struct print_t
{
void print() { std::cout << std::boolalpha << "From T " << std::is_same_v<T, const int&><< std::endl; }
};
template<typename T>
struct print_t<const T>
{
void print() { std::cout << std::boolalpha << "From const T " << std::is_same_v<T, int&><< std::endl; }
};
int main()
{
// From T true
print_t<const int&>().print();
// From const T false
print_t<const int>().print();
}
模板的底層實現是什么
學習資料
https://blog.csdn.net/jeffasd/article/details/84667090
https://ouuan.github.io/post/c-11-enable-if-的使用/
https://www.zhihu.com/column/c_1306966457508118528