C++学习---类型萃取---is_array && is_enum/is_union/is_class

158 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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也是类类型。