开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情
背景
定义在<type_traits>中,用于判断一个类型是否是数组类型,是否是枚举类型,是否是联合类型,是否是非联合类型的类类型,属于基础的类型判断。
代码实现
gcc官网:gcc.gnu.org/
gcc代码下载:mirrors.tuna.tsinghua.edu.cn/help/gcc.gi…
gcc版本代码:gcc-7.5.0(branch分支)
文件位置:gcc/libstdc++-v3/include/std/type_traits
注意:以下的代码实现省略了一些宏定义部分,实际的代码还是参考gcc源码,这里仅用作相关原理分析。
实现分析
is_array
/// is_array
template<typename>
struct is_array
: public false_type { };
template<typename _Tp, std::size_t _Size>
struct is_array<_Tp[_Size]>
: public true_type { };
template<typename _Tp>
struct is_array<_Tp[]>
: public true_type { };
is_array的普通版本继承了false_type,默认返回false类型,只有匹配到_Tp[_Size]或者_Tp[]是才特化为true_type,返回true类型,说明is_array只会识别这两种数组的类型。
is_enum/is_union/is_class
/// is_enum
template<typename _Tp>
struct is_enum
: public integral_constant<bool, __is_enum(_Tp)>
{ };
/// is_union
template<typename _Tp>
struct is_union
: public integral_constant<bool, __is_union(_Tp)>
{ };
/// is_class
template<typename _Tp>
struct is_class
: public integral_constant<bool, __is_class(_Tp)>
{ };
这三种类型之所以放到一起,是因为他们都是C++语言的关键字类型,所以它们的识别也是跟整个程序编译过程相关的。可以看到它们的实现继承了integral_constant模板,里面包含一个bool类型的值,这个值分别由函数__is_enum/__is_union/__is_class求出。
下面我们就来初步看一下这个__is_enum函数的调用过程(省略中间的调用过程),我们上面提到的三个内建函数就在c-common.c中定义。
实际上在代码语义分析阶段,会对语句进行分析,在这中间的过程中将会调用cp_parser_primary_expression函数进行语义分析,同时将相关的关键字进行标识,依次向下调用cp_parser_trait_expr,在该函数中将kind标识为对应的值,最后调用finish_trait_expr函数,在该函数中调用trait_expr_value计算bool值,实际上是使用前面记录的标识判断是否是对应的类型。
鉴于中间的调用过程很复杂,这里我们只是暂时初步的调用流程。实际上可以这样理解,对于这样的关键字类型,在编译阶段就可以计算出来,所以调用了编译阶段提供的内建函数。
//gcc/gcc/c-family/c-common.c
const struct c_common_resword c_common_reswords[] =
{
...
{ "__is_class", RID_IS_CLASS, D_CXXONLY },
{ "__is_enum", RID_IS_ENUM, D_CXXONLY },
{ "__is_union", RID_IS_UNION, D_CXXONLY },
...
}
//gcc/gcc/cp/parser.c
static cp_expr
cp_parser_primary_expression (cp_parser *parser,
bool address_p,
bool cast_p,
bool template_arg_p,
bool decltype_p,
cp_id_kind *idk)
{
...
/* Peek at the next token. */
token = cp_lexer_peek_token (parser->lexer);
switch ((int) token->type)
{
...
case RID_IS_CLASS:
...
case RID_IS_ENUM:
...
case RID_IS_UNION:
return cp_parser_trait_expr (parser, token->keyword);
}
/* Parse a trait expression.
Returns a representation of the expression, the underlying type
of the type at issue when KEYWORD is RID_UNDERLYING_TYPE. */
static tree
cp_parser_trait_expr (cp_parser* parser, enum rid keyword)
{
cp_trait_kind kind;
...
switch (keyword)
{
...
case RID_IS_CLASS:
kind = CPTK_IS_CLASS;
break;
...
case RID_IS_ENUM:
kind = CPTK_IS_ENUM;
break;
...
case RID_IS_UNION:
kind = CPTK_IS_UNION;
break;
...
}
...
/* Complete the trait expression, which may mean either processing
the trait expr now or saving it for template instantiation. */
switch (kind)
{
...
default:
return finish_trait_expr (kind, type1, type2);
}
}
//gcc/gcc/cp/semantics.c
tree
finish_trait_expr (cp_trait_kind kind, tree type1, tree type2)
{
switch (kind)
{
...
case CPTK_IS_CLASS:
case CPTK_IS_ENUM:
case CPTK_IS_UNION:
...
break;
...
}
return (trait_expr_value (kind, type1, type2)
? boolean_true_node : boolean_false_node);
}
}
/* Actually evaluates the trait. */
static bool
trait_expr_value (cp_trait_kind kind, tree type1, tree type2)
{
enum tree_code type_code1;
tree t;
type_code1 = TREE_CODE (type1);
switch (kind)
{
...
case CPTK_IS_CLASS:
return NON_UNION_CLASS_TYPE_P (type1);
case CPTK_IS_ENUM:
return type_code1 == ENUMERAL_TYPE;
case CPTK_IS_UNION:
return type_code1 == UNION_TYPE;
}
}
//gcc/gcc/cp/cp-tree.h
/* Nonzero if T is a class type but not an union. */
#define NON_UNION_CLASS_TYPE_P(T) \
(CLASS_TYPE_P (T) && TREE_CODE (T) != UNION_TYPE)
使用案例
#include <iostream>
#include <type_traits>
#include <array>
class ArrayTest{};
struct EnumTest{enum E{};};
enum E{};
enum class EnumClass:int{};
typedef union{
int a;
int b;
}UnionTest;
struct StructTest{};
class ClassTest{};
union UnionClass{class UC{};};
int main(){
std::cout << std::boolalpha;
std::cout << "------is_array------" << std::endl;
std::cout << "int:" << std::is_array<int>::value << std::endl;
std::cout << "int[]:" << std::is_array<int[]>::value << std::endl;
std::cout << "int[5]:" << std::is_array<int[5]>::value << std::endl;
std::cout << "class ArrayTest:" << std::is_array<ArrayTest>::value << std::endl;
std::cout << "class ArrayTest[]:" << std::is_array<ArrayTest[]>::value << std::endl;
std::cout << "class ArrayTest[5]:" << std::is_array<ArrayTest[5]>::value << std::endl;
std::cout << "std::array<int,5>:" << std::is_array<std::array<int,5>>::value << std::endl;
std::cout << "------is_enum------" << std::endl;
std::cout << "EnumTest:" << std::is_enum<EnumTest>::value << std::endl;
std::cout << "EnumTest member E:" << std::is_enum<EnumTest::E>::value << std::endl;
std::cout << "enum E:" << std::is_enum<E>::value << std::endl;
std::cout << "EnumClass:" << std::is_enum<EnumClass>::value << std::endl;
std::cout << "------is_union------" << std::endl;
std::cout << "UnionTest:" << std::is_union<UnionTest>::value << std::endl;
std::cout << "------is_class------" << std::endl;
std::cout << "StructTest:" << std::is_class<StructTest>::value << std::endl;
std::cout << "ClassTest:" << std::is_class<ClassTest>::value << std::endl;
std::cout << "ClassTest*:" << std::is_class<ClassTest*>::value << std::endl;
std::cout << "ClassTest&:" << std::is_class<ClassTest&>::value << std::endl;
std::cout << "const ClassTest:" << std::is_class<const ClassTest>::value << std::endl;
std::cout << "EnumClass:" << std::is_class<EnumClass>::value << std::endl;
std::cout << "UnionClass:" << std::is_class<UnionClass>::value << std::endl;
std::cout << "UnionClass::UC:" << std::is_class<UnionClass::UC>::value << std::endl;
std::cout << "incomplete struct IS:" << std::is_class<struct IS>::value << std::endl;
std::cout << "incomplete class CS:" << std::is_class<class IS>::value << std::endl;
return 0;
}
通过上面的例子,我们也能看到如下的几点:
-
is_array
- std::array并不是数组类型(这里指原生数组),std::array是STL实现的与原生数组类似的功能
- type[]和type[size]都能表示一个数组
- is_enum:只能识别枚举类型和枚举类
- is_union:识别联合类型
-
is_class
- struct也是类类型,特殊点在于它的成员变量都是public的
- 类指针,类引用都不是类类型
- const修饰的类也是类类型
- 枚举类不是类类型(class关键字类似与作用域,使得枚举类型的使用更加安全)
- 联合类型不是类类型,但联合类型中定义的类是类类型
- 在语句中定义不完整的struct和class也是类类型。
总结
-
is_array
- std::array并不是数组类型(这里指原生数组),std::array是STL实现的与原生数组类似的功能
- type[]和type[size]都能表示一个数组
- is_enum:只能识别枚举类型和枚举类
- is_union:识别联合类型
-
is_class
- struct也是类类型,特殊点在于它的成员变量都是public的
- 类指针,类引用都不是类类型
- const修饰的类也是类类型
- 枚举类不是类类型(class关键字类似与作用域,使得枚举类型的使用更加安全)
- 联合类型不是类类型,但联合类型中定义的类是类类型
- 在语句中定义不完整的struct和class也是类类型。