GCC-3.4.6源代码学习笔记(122)

5.12.4.2.2.        类名查找

cp_parser_class_name 的调用中,当且仅当关键字 typename 已经被使用来表示,在依赖类型中查找的名字应该被视为类型时, typename_keyword_p true ;当且仅当关键字 template 已经被使用来表示其次出现的名字是一个模板时, template_keyword_p true ;当且仅当下一个名字应该被视为类名( class-name ),即便它还被声明为其它类别的名字,时, type_p true ;如果 check_dependency_p false ,名字在依赖作用域( dependent scope )中查找;如果 class_head_p true ,这个类是在一个 class-head 中正在被定义的类。

 

11733 static tree

11734 cp_parser_class_name (cp_parser *parser,                                                in parser.c

11735                     bool typename_keyword_p,

11736                     bool template_keyword_p,

11737                     bool type_p,

11738                     bool check_dependency_p,

11739                     bool class_head_p,

11740                      bool is_declaration)

11741 {

11742    tree decl;

11743    tree scope;

11744    bool typename_p;

11745    cp_token *token;

11746

11747    /* All class-names start with an identifier.  */

11748    token = cp_lexer_peek_token (parser->lexer);

11749    if (token->type != CPP_NAME && token->type != CPP_TEMPLATE_ID)

11750    {

11751      cp_parser_error (parser, "expected class-name");

11752      return error_mark_node;

11753    }

11754     

11755    /* PARSER->SCOPE can be cleared when parsing the template-arguments

11756      to a template-id, so we save it here.  */

11757    scope = parser->scope;

11758    if (scope == error_mark_node)

11759      return error_mark_node;

11760   

11761    /* Any name names a type if we're following the `typename' keyword

11762      in a qualified name where the enclosing scope is type-dependent.  */

11763    typename_p = (typename_keyword_p && scope && TYPE_P (scope)

11764                 && dependent_type_p (scope));

11765    /* Handle the common case (an identifier, but not a template-id)

11766      efficiently.  */

 

11764 行, typename_p true ,如果我们在一个限定名里跟在关键字 typename 后面,并且当前作用域是类型依赖的。在 评估类型的依赖性 一节中,看到这表明该名字代表一个类型。回忆只有限定名才会把其所在作用域填充 parser->scope

 

cp_parser_class_name (continue)

 

11767    if (token->type == CPP_NAME

11768        && !cp_parser_nth_token_starts_template_argument_list_p (parser, 2))

11769    {

11770      tree identifier;

11771

11772      /* Look for the identifier.  */

11773      identifier = cp_parser_identifier (parser);

11774       /* If the next token isn't an identifier, we are certainly not

11775        looking at a class-name.  */

11776      if (identifier == error_mark_node)

11777        decl = error_mark_node;

11778      /* If we know this is a type-name, there's no need to look it

11779         up.  */

11780      else if (typename_p)

11781        decl = identifier;

11782      else

11783      {

11784        /* If the next token is a `::', then the name must be a type

11785          name.

11786

11787          [basic.lookup.qual]

11788

11789          During the lookup for a name preceding the :: scope

11790          resolution operator, object, function, and enumerator

11791          names are ignored.  */

11792         if (cp_lexer_next_token_is (parser->lexer, CPP_SCOPE))

11793           type_p = true;

11794         /* Look up the name.  */

11795         decl = cp_parser_lookup_name (parser, identifier,

11796                                 type_p,

11797                                 /*is_template=*/ false,

11798                                 /*is_namespace=*/ false,

11799                                 check_dependency_p);

11800      }

11801    }

5.12.4.2.2.1.  template-id

对于非 template-id 的情形, 名字查找的细节 一节已经进行了详细的描述。注意 11780 行,如果 typename_p true ,直接返回 identifier ,不做任何查找,因为 identifier 已经代表一个类型。

5.12.4.2.2.2.  Template-id

class-name 的另一个形式是 template-id ,它的语法如下(与【 3 】给出的有点不同):

template-id

   template-name < template-argument-list [opt] >

identifier             template-argument-lis t, template-argument

operator-function-id     template-argument

assignment-expression

type-id

id-expression

一个 conversion-function-id (转换操作符)不能是一个模板名,因为它们不能是构成 template-id 的部分。事实上,看到这样的代码:

a.operator K<int>()

conversion-function-id 是“ operator K<int> ”,而 K<int> 是一个 type-id 。不可能通过显式实参列表来调用一个模板化的 conversion-function-id ,因为唯一允许的模板实参是其要转换的类型。

 

cp_parser_class_name (continue)

 

11802    else

11803    {

11804      /* Try a template-id.  */

11805      decl = cp_parser_template_id (parser, template_keyword_p,

11806                               check_dependency_p,

11807                               is_declaration);

11808      if (decl == error_mark_node)

11809        return error_mark_node;

11810    }

 

此处参数 template_keyword_p 如果是 true 表示我们看到了关键字 template 。比如: T::template f<3> ()

 

7867 static tree

7868 cp_parser_template_id (cp_parser *parser,                                                  in parser.c

7869                      bool template_keyword_p,

7870                      bool check_dependency_p,

7871                      bool is_declaration)

7872 {

7873    tree template;

7874    tree arguments;

7875    tree template_id;

7876    ptrdiff_t start_of_id;

7877    tree access_check = NULL_TREE;

7878    cp_token *next_token, *next_token_2;

7879    bool is_identifier;

7880

7881    /* If the next token corresponds to a template-id, there is no need

7882      to reparse it.  */

7883    next_token = cp_lexer_peek_token (parser->lexer);

7884    if (next_token->type == CPP_TEMPLATE_ID)

7885    {

7886      tree value;

7887      tree check;

7888

7889      /* Get the stored value.  */

7890      value = cp_lexer_consume_token (parser->lexer)->value;

7891      /* Perform any access checks that were deferred.  */

7892      for (check = TREE_PURPOSE (value); check; check = TREE_CHAIN (check))

7893          perform_or_defer_access_check (TREE_PURPOSE (check),

7894                                     TREE_VALUE (check));

7895        /* Return the stored value.  */

7896        return TREE_VALUE (value);

7897    }

7898

7899    /* Avoid performing name lookup if there is no possibility of

7900      finding a template-id.  */

7901    if ((next_token->type != CPP_NAME && next_token->keyword != RID_OPERATOR)

7902        || (next_token->type == CPP_NAME

7903          && !cp_parser_nth_token_starts_template_argument_list_p

7904                   (parser, 2)))

7905    {

7906      cp_parser_error (parser, "expected template-id");

7907      return error_mark_node;

7908    }

7909

7910    /* Remember where the template-id starts.  */

7911    if (cp_parser_parsing_tentatively (parser)

7912        && !cp_parser_committed_to_tentative_parse (parser))

7913    {

7914      next_token = cp_lexer_peek_token (parser->lexer);

7915      start_of_id = cp_lexer_token_difference (parser->lexer,

7916                                        parser->lexer->first_token,

7917                                        next_token);

7918    }

7919    else

7920      start_of_id = -1;

7921

7922    push_deferring_access_checks (dk_deferred);

7923

7924    /* Parse the template-name.  */

7925    is_identifier = false;

7926    template = cp_parser_template_name (parser, template_keyword_p,

7927                                    check_dependency_p,

7928                                    is_declaration,

7929                                    &is_identifier);

7930    if (template == error_mark_node || is_identifier)

7931    {

7932      pop_deferring_access_checks ();

7933      return template;

7934    }

 

如果该 template-id 之前已经被成功解析过(对于尝试性解析器来说,对一段复杂模板代码的解析可能需要多次尝试,把已经成功解析的结构保存起来,可以加速编译的速度。类似的结构还有 nest-name-specifier ),解析器已经为把这些符号用结构 CPP_TEMLATE_ID 来代替。在后面我们可以看到该节点的细节。

如果未曾解析,则按部就班执行以下步骤。

5.12.4.2.2.2.1.          解析模板名

模板名可以是标识符或者 operator-function-id 。除此之外都不对。上面 7901 行的检查保证了这一点。另外,我们需要记住开始解析 template-id 的地方( 7915 行的 start_of_id ),一旦解析成功,我们需要把这些符号都替换掉。

 

8088 static tree

8089 cp_parser_template_name (cp_parser* parser,                                             in parser.c

8090                        bool template_keyword_p,

8091                        bool check_dependency_p,

8092                        bool is_declaration,

8093                         bool *is_identifier)

8094 {

8095    tree identifier;

8096    tree decl;

8097    tree fns;

8098

8099    /* If the next token is `operator', then we have either an

8100      operator-function-id or a conversion-function-id.  */

8101    if (cp_lexer_next_token_is_keyword (parser->lexer, RID_OPERATOR))

8102    {

8103      /* We don't know whether we're looking at an

8104        operator-function-id or a conversion-function-id.  */

8105      cp_parser_parse_tentatively (parser);

8106      /* Try an operator-function-id.  */

8107      identifier = cp_parser_operator_function_id (parser);

8108      /* If that didn't work, try a conversion-function-id.  */

8109      if (!cp_parser_parse_definitely (parser))

8110      {

8111        cp_parser_error (parser, "expected template-name");

8112        return error_mark_node;

8113      }

8114    }

8115    /* Look for the identifier.  */

8116    else

8117      identifier = cp_parser_identifier (parser);

5.12.4.2.2.1.1.1.    Operator-function-id

如果看到关键字 operator ,解析器将尝试 operator-function-id ,其规则如下:

operator-function-id:

     operator operator

注意第二个 operator 不是终结符,它包含了如下的终结符:

operator:

new delete new[] delete[] + - * / % ^ & | ~ ! = < >

     += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> () []

 

7307 static tree

7308 cp_parser_operator_function_id (cp_parser* parser)                                     in parser.c

7309 {

7310     /* Look for the `operator' keyword.  */

7311    if (!cp_parser_require_keyword (parser, RID_OPERATOR, "`operator'"))

7312      return error_mark_node;

7313     /* And then the name of the operator itself.  */

7314    return cp_parser_operator (parser);

7315 }

 

初始化操作符数据 一节,关于操作符的信息从配置文件 operator.def 中提取出来,并且记录在全局数组 operator_name_info assignment_operator_name_info 。在 cp_parser_operator 中,该函数将返回该操作符对应的标识符节点,这个标识符是唯一的。而后编译器将根据该标识符产生代码。

 

7332 static tree

7333 cp_parser_operator (cp_parser* parser)                                                              in parser.c

7334 {

7335    tree id = NULL_TREE;

7336    cp_token *token;

7337

7338    /* Peek at the next token.  */

7339    token = cp_lexer_peek_token (parser->lexer);

7340    /* Figure out which operator we have.  */

7341    switch (token->type)

7342    {

7343      case CPP_KEYWORD:

7344      {

7345        enum tree_code op;

7346

7347        /* The keyword should be either `new' or `delete'.  */

7348        if (token->keyword == RID_NEW)

7349          op = NEW_EXPR;

7350        else if (token->keyword == RID_DELETE)

7351          op = DELETE_EXPR;

7352        else

7353          break ;

7354

7355        /* Consume the `new' or `delete' token.  */

7356        cp_lexer_consume_token (parser->lexer);

7357

7358        /* Peek at the next token.  */

7359        token = cp_lexer_peek_token (parser->lexer);

7360        /* If it's a `[' token then this is the array variant of the

7361           operator.  */

7362        if (token->type == CPP_OPEN_SQUARE)

7363        {

7364          /* Consume the `[' token.  */

7365          cp_lexer_consume_token (parser->lexer);

7366          /* Look for the `]' token.  */

7367          cp_parser_require (parser, CPP_CLOSE_SQUARE, "`]'");

7368          id = ansi_opname (op == NEW_EXPR

7369                          ? VEC_NEW_EXPR : VEC_DELETE_EXPR);

7370        }

7371        /* Otherwise, we have the non-array variant.  */

7372        else

7373          id = ansi_opname (op);

7374

7375        return id;

7376      }

7377

7378      case CPP_PLUS:

7379        id = ansi_opname (PLUS_EXPR);

7380        break ;

         …

7522      case CPP_OPEN_PAREN:

7523        /* Consume the `('.  */

7524        cp_lexer_consume_token (parser->lexer);

7525        /* Look for the matching `)'.  */

7526        cp_parser_require (parser, CPP_CLOSE_PAREN, "`)'");

7527        return ansi_opname (CALL_EXPR);

7528

7529      case CPP_OPEN_SQUARE:

7530         /* Consume the `['.  */

7531        cp_lexer_consume_token (parser->lexer);

7532         /* Look for the matching `]'.  */

7533        cp_parser_require (parser, CPP_CLOSE_SQUARE, "`]'");

7534        return ansi_opname (ARRAY_REF);

7535

7536        /* Extensions.  */

7537      case CPP_MIN:

7538        id = ansi_opname (MIN_EXPR);

7539        break ;

7540

7541      case CPP_MAX:

7542        id = ansi_opname (MAX_EXPR);

7543         break ;

7544

7545      case CPP_MIN_EQ:

7546        id = ansi_assopname (MIN_EXPR);

7547        break ;

7548

7549      case CPP_MAX_EQ:

7550        id = ansi_assopname (MAX_EXPR);

7551         break ;

7552

7553      default :

7554        /* Anything else is an error.  */

7555        break ;

7556    }

7557

7558    /* If we have selected an identifier, we need to consume the

7559      operator token.  */

7560    if (id)

7561      cp_lexer_consume_token (parser->lexer);

7562    /* Otherwise, no valid operator name was present.  */

7563    else

7564    {

7565      cp_parser_error (parser, "expected operator");

7566      id = error_mark_node;

7567    }

7568

7569    return id;

7570 }

 

显然,宏 ansi_opname ansi_assopname 从数组中返回标识符。

 

868  #define ansi_opname(CODE) /                                                                     in cp-tree.h

869    (operator_name_info [(int) (CODE)].identifier)

870  #define ansi_assopname (CODE) /

871    (assignment_operator_name_info [(int) (CODE)].identifier)

5.12.4.2.2.1.1.2.    标识符

根据【 3 】,当一个成员模板特化的名字,在一个 postfix-expression 中出现在“ . ”或“ -> ”之后,或者在一个 qualified-id 中在 nested-name-specifier 之后;并且该 postfix-expression qualified-id 显式地依赖于一个模板参数,该成员模板名必须有关键字 template 作为前缀。否则该名字被假定为一个非模板名。例如:

class X {

public :

template <size_t> X* alloc();

template <size_t> static X* adjust();

};

template <class T> void f(T* p) {

T* p1 = p->alloc<200>();      // ill-formed: < means less than

T* p2 = p->template alloc<200>(); // OK: < starts template argument list

T::adjust<100>();     // ill-formed: < means less than

T::template adjust<100>();      // OK: < starts template argument list

}

以“ T::adjust<100>(); ”为例,当解析这个语句时,此时, is_declaration true ,又因为没有看到 template template_keyword_p false ,“ parser->scope ”则指向 T ,“ TYPE_P (parser->scope) ”及“ dependent_type_p (parser->scope) ”返回 true ,因此满足 8145 行的条件。作为一个显著例外,构造函数和析构函数不要求在其名字前使用关键字 template ,因为它们的名字都是依赖性的。对于错误的情形, 8153 8198 行的代码给出合适的错误信息,并消耗掉构成错误结构的符号。

 

cp_parser_template_name (continue)

 

8119     /* If we didn't find an identifier, we don't have a template-id.  */

8120    if (identifier == error_mark_node)

8121      return error_mark_node;

8122

8123    /* If the name immediately followed the `template' keyword, then it

8124      is a template-name. However, if the next token is not `<', then

8125      we do not treat it as a template-name, since it is not being used

8126      as part of a template-id. This enables us to handle constructs

8127      like:

8128

8129        template <typename T> struct S { S(); };

8130        template <typename T> S<T>::S();

8131

8132      correctly. We would treat `S' as a template -- if it were `S<T>'

8133      -- but we do not if there is no `<'.   */

8134

8135    if (processing_template_decl

8136        && cp_parser_nth_token_starts_template_argument_list_p (parser, 1))

8137    {

8138      /* In a declaration, in a dependent context, we pretend that the

8139        "template" keyword was present in order to improve error

8140        recovery. For example, given:

8141       

8142          template <typename T> void f(T::X<int>);

8143       

8144        we want to treat "X<int>" as a template-id.  */

8145      if (is_declaration

8146         && !template_keyword_p

8147         && parser->scope && TYPE_P (parser->scope)

8148         && check_dependency_p

8149         && dependent_type_p (parser->scope)

8150         /* Do not do this for dtors (or ctors), since they never

8151           need the template keyword before their name.  */

8152         && !constructor_name_p (identifier, parser->scope))

8153      {

8154        ptrdiff_t start;

8155        cp_token* token;

8156         /* Explain what went wrong.  */

8157        error ("non-template `%D' used as template", identifier);

8158        inform ("use `%T::template %D' to indicate that it is a template",

8159               parser->scope, identifier);

8160        /* If parsing tentatively, find the location of the "<"

8161          token.  */

8162        if (cp_parser_parsing_tentatively (parser)

8163           && !cp_parser_committed_to_tentative_parse (parser))

8164        {

8165          cp_parser_simulate_error (parser);

8166          token = cp_lexer_peek_token (parser->lexer);

8167          token = cp_lexer_prev_token (parser->lexer, token);

8168          start = cp_lexer_token_difference (parser->lexer,

8169                                       parser->lexer->first_token,

8170                                      token);

8171        }

8172        else

8173          start = -1;

8174        /* Parse the template arguments so that we can issue error

8175          messages about them.  */

8176        cp_lexer_consume_token (parser->lexer);

8177        cp_parser_enclosed_template_argument_list (parser);

8178        /* Skip tokens until we find a good place from which to

8179          continue parsing.  */

8180        cp_parser_skip_to_closing_parenthesis (parser,

8181                                         /*recovering=*/ true,

8182                                          /*or_comma=*/ true,

8183                                         /*consume_paren=*/ false);

8184         /* If parsing tentatively, permanently remove the

8185          template argument list. That will prevent duplicate

8186          error messages from being issued about the missing

8187          "template" keyword.  */

8188        if (start >= 0)

8189        {

8190          token = cp_lexer_advance_token (parser->lexer,

8191                                       parser->lexer->first_token,

8192                                      start);

8193          cp_lexer_purge_tokens_after (parser->lexer, token);

8194        }

8195        if (is_identifier)

8196          *is_identifier = true;

8197         return identifier;

8198      }

8199

8200      /* If the "template" keyword is present, then there is generally

8201        no point in doing name-lookup, so we just return IDENTIFIER.

8202        But, if the qualifying scope is non-dependent then we can

8203        (and must) do name-lookup normally.  */

8204      if (template_keyword_p

8205         && (!parser->scope

8206             || (TYPE_P (parser->scope)

8207               && dependent_type_p (parser->scope))))

8208         return identifier;

8209    }

 

如果找到的标识符没有依赖性,应该立刻查找以解析这个名字。如果该名字是有效的,那么 cp_parser_lookup_name 将返回代表当前与该名字绑定的声明的节点,并且注意到在此刻我们接受任何匹配的东西。类似的,如果我们找到的是一个 baselink ,而它指向一个重载函数链表,前端则进入这个链表并找出作为模板声明的那个,因为我们正在查找模板名,如果找不到就给出错误消息。

 

cp_parser_template_name (continue)

 

8211    /* Look up the name.  */

8212    decl = cp_parser_lookup_name (parser, identifier,

8213                               /*is_type=*/ false,

8214                               /*is_template=*/ false,

8215                               /*is_namespace=*/ false,

8216                               check_dependency_p);

8217    decl = maybe_get_template_decl_from_type_decl (decl);

8218

8219    /* If DECL is a template, then the name was a template-name.  */

8220    if (TREE_CODE (decl) == TEMPLATE_DECL)

8221      ;

8222    else

8223    {

8224      tree fn = NULL_TREE;

8225

8226      /* The standard does not explicitly indicate whether a name that

8227        names a set of overloaded declarations, some of which are

8228        templates, is a template-name. However, such a name should

8229        be a template-name; otherwise, there is no way to form a

8230        template-id for the overloaded templates.  */

8231      fns = BASELINK_P (decl) ? BASELINK_FUNCTIONS (decl) : decl;

8232      if (TREE_CODE (fns) == OVERLOAD)

8233        for (fn = fns; fn; fn = OVL_NEXT (fn))

8234          if (TREE_CODE (OVL_CURRENT (fn)) == TEMPLATE_DECL)

8235            break ;

8236

8237      if (!fn)

8238      {

8239        /* Otherwise, the name does not name a template.  */

8240        cp_parser_error (parser, "expected template-name");

8241        return error_mark_node;

8242      }

8243    }

8244

8245    /* If DECL is dependent, and refers to a function, then just return

8246      its name; we will look it up again during template instantiation.  */

8247    if (DECL_FUNCTION_TEMPLATE_P (decl) || !DECL_P (decl))

8248    {

8249      tree scope = CP_DECL_CONTEXT (get_first_fn (decl));

8250      if (TYPE_P (scope) && dependent_type_p (scope))

8251        return identifier;

8252    }

8253

8254    return decl;

8255 }

 

对于类模板,我们已经看到,至少有 2 TYPE_DECL 被构建。一个对应于声明,而另一个表示对自己的引用;两者都引用类的 RECORD_TYPE 节点。此处被提取的 TYPE_DECL 是对自己引用的那个(看到从类节点的 bindings 域,导向的是对自己引用的 TYPE_DECL ),不过这个节点对于模板声明并不合适。因此由 CLASSTYPE_TI_TEMPLATE 进一步返回 TEMPLATE_DECL 节点。

 

4105 tree

4106 maybe_get_template_decl_from_type_decl (tree decl)                                          in pt.c

4107 {

4108    return (decl != NULL_TREE

4109          && TREE_CODE (decl) == TYPE_DECL

4110          && DECL_ARTIFICIAL (decl)

4111          && CLASS_TYPE_P (TREE_TYPE (decl))

4112          && CLASSTYPE_TEMPLATE_INFO (TREE_TYPE (decl)))

4113              ? CLASSTYPE_TI_TEMPLATE (TREE_TYPE (decl)) : decl;

4114 }

 

在为模板名找到合适的节点后,这个名字必须后跟一对尖括号。不过,有一个特殊的情形必须考虑。就是当看到“ <:: ”时,如果允许复合字母拼写( digraph spelling ),“ <: ”将被解释为“ [ ”,从而给出结果“ [: ”(这由词法分析器完成)。因此如果我们看到“ [: ”跟在模板名后,我们需要将它转换回“ <:: ”(如果程序员误写了“ [: ”,解析器可以自动地纠正这个错误,看上去很酷 so cool )。这个不好的用法将导致一个警告。

 

cp_parser_template_id (continue)

 

7936    /* If we find the sequence `[:' after a template-name, it's probably

7937      a digraph-typo for `< ::'. Substitute the tokens and check if we can

7938      parse correctly the argument list.  */

7939    next_token = cp_lexer_peek_nth_token (parser->lexer, 1);

7940    next_token_2 = cp_lexer_peek_nth_token (parser->lexer, 2);

7941    if (next_token->type == CPP_OPEN_SQUARE

7942        && next_token->flags & DIGRAPH

7943         && next_token_2->type == CPP_COLON

7944        && !(next_token_2->flags & PREV_WHITE))

7945    {

7946      cp_parser_parse_tentatively (parser);

7947      /* Change `:' into `::'.  */

7948      next_token_2->type = CPP_SCOPE;

7949      /* Consume the first token (CPP_OPEN_SQUARE - which we pretend it is

7950        CPP_LESS.  */

7951      cp_lexer_consume_token (parser->lexer);

7952      /* Parse the arguments.  */

7953      arguments = cp_parser_enclosed_template_argument_list (parser);

7954      if (!cp_parser_parse_definitely (parser))

7955      {

7956        /* If we couldn't parse an argument list, then we revert our changes

7957          and return simply an error. Maybe this is not a template-id

7958          after all.  */

7959        next_token_2->type = CPP_COLON;

7960        cp_parser_error (parser, "expected `<'");

7961        pop_deferring_access_checks ();

7962        return error_mark_node;

7963      }

7964      /* Otherwise, emit an error about the invalid digraph, but continue

7965        parsing because we got our argument list.  */

7966      pedwarn ("`<::' cannot begin a template-argument list");

7967      inform ("`<:' is an alternate spelling for `['. Insert whitespace "

7968             "between `<' and `::'");

7969      if (!flag_permissive )

7970      {

7971         static bool hint;

7972        if (!hint)

7973        {

7974          inform ("(if you use `-fpermissive' G++ will accept your code)");

7975          hint = true;

7976        }

7977      }

7978    }

7979    else

7980    {

7981      /* Look for the `<' that starts the template-argument-list.  */

7982      if (!cp_parser_require (parser, CPP_LESS, "`<'"))

7983      {

7984        pop_deferring_access_checks ();

7985        return error_mark_node;

7986      }

7987      /* Parse the arguments.  */

7988      arguments = cp_parser_enclosed_template_argument_list (parser);

7989    }

 

<> ”之间的就是实参列表。对于函数模板,实参列表可以是空的,只要从函数的调用实参可以无二义性地推导出模板实参。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值