前言
想要了解模板编程,肯定绕不过如下基础判断模板。
一、部分源码
// xtr1common internal header (core)
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#pragma once
#ifndef _XTR1COMMON_
#define _XTR1COMMON_
#include <yvals_core.h>
#if _STL_COMPILER_PREPROCESSOR
#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
_STL_DISABLE_CLANG_WARNINGS
#pragma push_macro("new")
#undef new
_STD_BEGIN
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;
}
};
template <bool _Val>
using bool_constant = integral_constant<bool, _Val>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
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;
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;
#ifdef __clang__
template <class _Ty1, class _Ty2>
_INLINE_VAR constexpr bool is_same_v = __is_same(_Ty1, _Ty2);
template <class _Ty1, class _Ty2>
struct is_same : bool_constant<__is_same(_Ty1, _Ty2)> {};
#else // ^^^ Clang / not Clang vvv
template <class, class>
_INLINE_VAR constexpr bool is_same_v = false; // determine whether arguments are the same type
template <class _Ty>
_INLINE_VAR constexpr bool is_same_v<_Ty, _Ty> = true;
template <class _Ty1, class _Ty2>
struct is_same : bool_constant<is_same_v<_Ty1, _Ty2>> {};
#endif // __clang__
二、详解
1.各种宏定义的作用和使用
- #if
2. 使用方式。
#if true
只有当条件为true时才会执行此部分
#endif
3. 等价方式
#if defined (x) <==> #ifdef
#if !defined(x) <==> #ifndef
4. 使用例子
#define len 3
#define max 6
#if len <max
#undef max //取消宏定义
#define max len
#endif //结束#if的范围
- #define
宏定义。只需额外了解##可以拼接, \反斜杠可以跨行就可以(字符串里\也可以跨行)
#define splice(a,b) a##b //拼接
#define a xxx \
xxx
\可以跨行
- pragma
详细了解请点击- #pragma once
- 头文件只导入一次
- #pragma pack( [show] | [push | pop] [, identifier], n )
- 设置字节对齐方式
1. #pragma pack(show) 以警告信息的形式显示当前字节对齐的值. 2. #pragma pack(push, identifier, n) 将当前字节对齐值压入编译栈栈顶, 然后将栈中保存该值的位置标识为 identifier, 再将 n 设为当前值. 3. \#pragma pack(pop, identifier, n) 将编译栈栈中标识为 identifier 位置的值弹出, 然后丢弃, 再将 n 设为当前值. 注意, 如果栈中所标识的位置之上还有值, 那会先被弹出并丢弃. 4. 如果在栈中没有找到 pop 中的标识符, 则编译器忽略该指令, 而且不会弹出任何值.
- 设置字节对齐方式
- #pragma warning
- 设置报警级别,屏蔽和打开某个报警
1. #pragma warning(push, n) 1. n = 1~4 ,警告等级 2. #pragma warning(pop) 3. #pragma warning(disable : n) 1. n = 报警信息例如(4180),屏蔽某个报警 4. \#pragma warning(default: n) 1. n = 报警信息例如(4180),启用某个报警
- 设置报警级别,屏蔽和打开某个报警
- #pragma push_macro(“new”)/#pragma pop_macro(“new”)
- 去除和恢复之前的的宏定义(#undef new 为了安全起见,如果没有起作用就直接取消new的宏定义。)
- 去除和恢复之前的的宏定义(#undef new 为了安全起见,如果没有起作用就直接取消new的宏定义。)
- #pragma once
2.源码解读
-
萃取基类
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; } }; 将本身,值和类型分别定义成三个类型。分别为type,value_type和value
-
利用萃取基类设计的bool类型对象,是很多模板判断的基础基类
template <bool _Val> using bool_constant = integral_constant<bool, _Val>; 对萃取基类的特化,只有类型为bool类型才会被调用 using true_type = bool_constant<true>; using false_type = bool_constant<false>; 调用上述模板新定义两个类型。bool类型,值为true和false
-
enable_if 判断一个类型能否被真正定义出来。就是利用上述ture_type和false_type来实现的。设计一种判断方式去继承true_type和false_type,当满足条件就继承true_type,否则就继承false_type。借助enable_if,就可以判断是否能获取到类型。你再设计几个模板方法,只有满足条件的那个enable_if才会生成类型,才能匹配到满足的那个模板。利用了编译器匹配机制SFINAE(当调用模板函数时编译器会根据传入参数推导最合适的模板函数,在这个推导过程中如果某一个或者某几个模板函数推导出来是编译无法通过的,只要有一个可以正确推导出来,那么那几个推导得到的可能产生编译错误的模板函数并不会引发编译错误。)
template <bool _Test, class _Ty = void> struct enable_if {}; // no member "type" when !_Test 当bool类型为false时调用,就是什么类型都不会生成。 template <class _Ty> struct enable_if<true, _Ty> { // type is _Ty for _Test using type = _Ty; }; 当bool类型为ture时会匹配到,因为特化更符合机制。会产生一个新的类型。 template <bool _Test, class _Ty = void> using enable_if_t = typename enable_if<_Test, _Ty>::type; 对新类型重新命名
-
选择t1还是t2的类型
template <bool _Test, class _Ty1, class _Ty2> struct conditional { // Choose _Ty1 if _Test is true, and _Ty2 otherwise using type = _Ty1; }; 普通版本定义了三个变量,利用第一个变量进行特化匹配。当第一个参数为true时对t1重新定义一个类型 当bool 为false时会特化调用下面那个。 template <class _Ty1, class _Ty2> struct conditional<false, _Ty1, _Ty2> { using type = _Ty2; }; 特化版,当第一个参数为flae时就会调用。选择t2的类型重新定义一个新的类型。 template <bool _Test, class _Ty1, class _Ty2> using conditional_t = typename conditional<_Test, _Ty1, _Ty2>::type; 对选择的类型重新定义。
-
参数的类型是否一样(利用模板类型推导,编译器自己识别)
template <class, class> _INLINE_VAR constexpr bool is_same_v = false; // determine whether arguments are the same type 编译器推导出是两个类型,就设置错误。 template <class _Ty> _INLINE_VAR constexpr bool is_same_v<_Ty, _Ty> = true; 上一个的特化版本,当两个类型一样时就会调用。 编译器推导出是一个类型就设置正确 template <class _Ty1, class _Ty2> struct is_same : bool_constant<is_same_v<_Ty1, _Ty2>> {}; 当t1和t2类型调用返回true,因为特化匹配优先级大于普通模板
3.模板代码欣赏(标签分发-解决函数无法重载)
https://zhuanlan.zhihu.com/p/268600376?utm_source=qq
struct NormalVersionTag {};
struct IntPartialVersionTag {};
//先定义两个类型不同类基类
template<class T> struct TagDispatchTrait {
using Tag = NormalVersionTag;
};
//定义一个模板基类,其类型默认为NormalVersionTag
template<>
struct TagDispatchTrait<int> {
using Tag = IntPartialVersionTag;
};
//上一个模板的特化。当T的类型为int时其类型被定义为IntPartialVersionTag
template <typename A, typename B>
inline void internal_f(A a, B b, NormalVersionTag) {
std::cout << "Normal version." << std::endl;
}
template <typename A, typename B>
inline void internal_f(A a, B b, IntPartialVersionTag) {
std::cout << "Partial version." << std::endl;
}
//上述就是利用函数重载,利用两个函数第三个参数定义了不同的类型进行区分
template <typename A, typename B>
void f(A a, B b) {
return internal_f(a, b, typename TagDispatchTrait<B>::Tag {});
}
//定义了一个类似于工厂的类,通过获取B的类型进行模板选择,再通过::访问类内对象获取自己制定的类型,最后通过重载选择需要的那个函数。
// 测试代码
int a = 10;
double b = 12;
f(a, b);
f(a, a);
真心觉得这个用法很有意思。
总结
学习这些模板设置的方式,时间久了自然就会理解模板如何使用。