为什么在需要使用‘template as a disambiguator’

一、为什么用这个标题

标题中的Chinglish并不是为了装逼,而是为了更加原汁原味的表达这个问题的出现场景,这个说法来自gcc的提示:
gcc-4.4.7\gcc\cp\parser.c
static bool
cp_parser_optional_template_keyword (cp_parser *parser)
{
if (cp_lexer_next_token_is_keyword (parser->lexer, RID_TEMPLATE))
{
/* The `template' keyword can only be used within templates;
outside templates the parser can always figure out what is a
template and what is not. */
if (!processing_template_decl)
{
cp_token *token = cp_lexer_peek_token (parser->lexer);
error ("%H%<template%> (as a disambiguator) is only allowed "
"within templates", &token->location);
/* If this part of the token stream is rescanned, the same
error message would be generated. So, we purge the token
from the stream. */
cp_lexer_purge_token (parser->lexer);
return false;
}
……
}

二、为什么注意到这个问题

由于std::tr1的function主要是基于tuple实现的,所以自己想模拟下tuple的实现方法,在实现的时候发现调用的时候经常出错,在网上搜索了下该问题,需要使用template关键字说明。注意下面代码中的
37 return tupleimp<idx + 1, typelist...>::template get<T, index>();
的template关键字。

tsecer@harry: cat -n mytuple.cpp
1 template<int x, typename... typelist> struct tupleimp;
2 struct callbase{};
3 struct returnthis{};
4
5 template<bool b>
6 struct chose
7 {
8 typedef callbase TYPE;
9 };
10
11 template<>
12 struct chose<true>
13 {
14 typedef returnthis TYPE;
15 };
16
17 template<int x>
18 struct tupleimp<x>
19 {
20 };
21
22 template <int idx, typename HEAD, typename... typelist>
23 struct tupleimp<idx, HEAD, typelist...>:public tupleimp<idx + 1, typelist...>
24 {
25 tupleimp(HEAD h, typelist... args):m_h(h), tupleimp<idx + 1, typelist...>(args...){}
26 HEAD m_h;
27
28 template<typename T, int index>
29 T get( ){
30 typename chose<index==idx>::TYPE t;
31 return fork<T,index>(t);
32 }
33
34 template<typename T, int index>
35 T fork(callbase c) {
36
37 return tupleimp<idx + 1, typelist...>::template get<T, index>();
38 }
39
40 template<typename T, int index>
41 T fork(returnthis c) {
42 return m_h;
43 }
44
45
46 };
47 template <typename T>
48 int foo()
49 {
50 return 0;
51 }
52 int x = foo<int>();
53 template<typename ... typelist>
54 struct tuple: public tupleimp<0, typelist...>
55 {
56 tuple(typelist... args):tupleimp<0, typelist...>(args...){}
57 };
58
59 template<int index, typename T, typename TUPLE>
60 T get(TUPLE &t)
61 {
62 return t.get<T, index>();
63 }
64
65 tuple<float, char, int> tup(.0, 'z', 66);
66
67 int main()
68 {
69 return get<2, int>(tup);
70 }
tsecer@harry: g++ -std=c++11 mytuple.cpp
tsecer@harry: ./a.out
tsecer@harry: echo $?
66

三、一个更场景的经典问题:为什么需要typename声明

看下面的例子,在C::foo函数中,当编译器解析到TYPE::T的时候,它并不知道它的“语法属性”是什么。所谓的“语法属性”是指它是一个标识符(identifier)还是一个类型(type):例如在A中,T是一个类型,而在B中T是一个变量。大家可能会觉得:这个校验反正是在真正提供类型的时候再校验,此时为什么需要明确说明它是一个类型还是一个变量呢?还是刚才强调的,语法解析的时候首先要生成一个“语法树”,语法数中的元素都是要有语法属性的,这个属性将会驱动语义的解析和动作的执行。更直观的说,如果不确定语法属性,它只是一个词法单位,并不具有编译的解析意义。
tsecer@harry: cat -n why.disambigous.cpp
1 struct A
2 {
3 int T;
4 };
5
6 struct B
7 {
8 typedef int T;
9 };
10
11 template <typename TYPE>
12 struct C
13 {
14 void foo()
15 {
16 TYPE::T * 10;
17 TYPE::T * t;
18 }
19 };
tsecer@harry: LC_ALL=C gcc -c why.disambigous.cpp
why.disambigous.cpp: In member function 'void C<TYPE>::foo()':
why.disambigous.cpp:17:13: error: 't' was not declared in this scope
TYPE::T * t;
^
tsecer@harry:
注意:其中的TYPE::T * 10;并没有错误,说明在缺省没有指明的情况下,编译器将它假设为一个identifier(而不是类型)。

四、gcc对template id的解析

这里主要的逻辑在于:如果声明了template,那么编译器会把template后面的名字和<arglist...>解析为一个整体的template id(template id是由 name和尖括号中的内容作为一个整体组成一个template id)。反过来说,如果此处没有把这个尖括号内的内容消耗掉,那么此时尖括号会分别作为 小于号(<)和大于号(>)来处理,并引起语法错误。
static tree
cp_parser_template_id (cp_parser *parser,
bool template_keyword_p,
bool check_dependency_p,
bool is_declaration)
{
……
/* Parse the template-name. */
is_identifier = false;
token = cp_lexer_peek_token (parser->lexer);
templ = cp_parser_template_name (parser, template_keyword_p,
check_dependency_p,
is_declaration,
&is_identifier);
if (templ == error_mark_node || is_identifier)
{
pop_deferring_access_checks ();
return templ;
}

……

else
{
/* Look for the `<' that starts the template-argument-list. */
if (!cp_parser_require (parser, CPP_LESS, "%<<%>"))
{
pop_deferring_access_checks ();
return error_mark_node;
}
/* Parse the arguments. */
arguments = cp_parser_enclosed_template_argument_list (parser);
}

……

/* If parsing tentatively, replace the sequence of tokens that makes
up the template-id with a CPP_TEMPLATE_ID token. That way,
should we re-parse the token stream, we will not have to repeat
the effort required to do the parse, nor will we issue duplicate
error messages about problems during instantiation of the
template. */
if (start_of_id)
{
cp_token *token = cp_lexer_token_at (parser->lexer, start_of_id);

/* Reset the contents of the START_OF_ID token. */
token->type = CPP_TEMPLATE_ID;
/* Retrieve any deferred checks. Do not pop this access checks yet
so the memory will not be reclaimed during token replacing below. */
token->u.tree_check_value = GGC_CNEW (struct tree_check);
token->u.tree_check_value->value = template_id;
token->u.tree_check_value->checks = get_deferred_access_checks ();
token->keyword = RID_MAX;

/* Purge all subsequent tokens. */
cp_lexer_purge_tokens_after (parser->lexer, start_of_id);

/* ??? Can we actually assume that, if template_id ==
error_mark_node, we will have issued a diagnostic to the
user, as opposed to simply marking the tentative parse as
failed? */
if (cp_parser_error_occurred (parser) && template_id != error_mark_node)
error ("%Hparse error in template argument list",
&token->location);
}

……

}

五、C++规范中对该问题的说明

下面(14.2 Names of template specializations一节)example中说明了模板的左尖括号会被当做小于号:
When the name of a member template specialization appears after . or -> in a postfix-expression or after a
nested-name-specifier in a qualified-id, and the object or pointer expression of the postfix-expression or the
nested-name-specifier in the qualified-id depends on a template parameter (14.6.2) but does not refer to a
member of the current instantiation (14.6.2.1), the member template name must be prefixed by the keyword
template. Otherwise the name is assumed to name a non-template. [ Example:
struct X {
template<std::size_t> X* alloc();
template<std::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
}
—end example ]

六、例子

对于下面的代码
template<typename T>
struct A
{
int foo()
{
return T::template bar<int>()
+ T::baz<T::bzz>();
}
};

1、名称的解析

cp_parser_qualifying_entity==>>cp_parser_class_name==>>cp_parser_template_id==>>cp_parser_template_name
在解析T::template bar<int>()的时候,函数从
"
if (start)
cp_lexer_purge_tokens_after (parser->lexer, start);
if (is_identifier)
*is_identifier = true;
return identifier;
"
这里返回。
当执行T::baz<T::bzz>()的时候,此处没有返回,继续往下面执行,返回的节点为null。
static tree
cp_parser_template_name (cp_parser* parser,
bool template_keyword_p,
bool check_dependency_p,
bool is_declaration,
bool *is_identifier)
{
tree identifier;
tree decl;
tree fns;
cp_token *token = cp_lexer_peek_token (parser->lexer);

/* If the next token is `operator', then we have either an
operator-function-id or a conversion-function-id. */
if (cp_lexer_next_token_is_keyword (parser->lexer, RID_OPERATOR))
{
/* We don't know whether we're looking at an
operator-function-id or a conversion-function-id. */
cp_parser_parse_tentatively (parser);
/* Try an operator-function-id. */
identifier = cp_parser_operator_function_id (parser);
/* If that didn't work, try a conversion-function-id. */
if (!cp_parser_parse_definitely (parser))
{
cp_parser_error (parser, "expected template-name");
return error_mark_node;
}
}
/* Look for the identifier. */
else
identifier = cp_parser_identifier (parser);

/* If we didn't find an identifier, we don't have a template-id. */
if (identifier == error_mark_node)
return error_mark_node;

/* If the name immediately followed the `template' keyword, then it
is a template-name. However, if the next token is not `<', then
we do not treat it as a template-name, since it is not being used
as part of a template-id. This enables us to handle constructs
like:

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

correctly. We would treat `S' as a template -- if it were `S<T>'
-- but we do not if there is no `<'. */

if (processing_template_decl
&& cp_parser_nth_token_starts_template_argument_list_p (parser, 1))
{
/* In a declaration, in a dependent context, we pretend that the
"template" keyword was present in order to improve error
recovery. For example, given:

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

we want to treat "X<int>" as a template-id. */
if (is_declaration
&& !template_keyword_p
&& parser->scope && TYPE_P (parser->scope)
&& check_dependency_p
&& dependent_scope_p (parser->scope)
/* Do not do this for dtors (or ctors), since they never
need the template keyword before their name. */
&& !constructor_name_p (identifier, parser->scope))
{
cp_token_position start = 0;

/* Explain what went wrong. */
error ("%Hnon-template %qD used as template",
&token->location, identifier);
inform (input_location, "use %<%T::template %D%> to indicate that it is a template",
parser->scope, identifier);
/* If parsing tentatively, find the location of the "<" token. */
if (cp_parser_simulate_error (parser))
start = cp_lexer_token_position (parser->lexer, true);
/* Parse the template arguments so that we can issue error
messages about them. */
cp_lexer_consume_token (parser->lexer);
cp_parser_enclosed_template_argument_list (parser);
/* Skip tokens until we find a good place from which to
continue parsing. */
cp_parser_skip_to_closing_parenthesis (parser,
/*recovering=*/true,
/*or_comma=*/true,
/*consume_paren=*/false);
/* If parsing tentatively, permanently remove the
template argument list. That will prevent duplicate
error messages from being issued about the missing
"template" keyword. */
if (start)
cp_lexer_purge_tokens_after (parser->lexer, start);
if (is_identifier)
*is_identifier = true;
return identifier;
}

/* If the "template" keyword is present, then there is generally
no point in doing name-lookup, so we just return IDENTIFIER.
But, if the qualifying scope is non-dependent then we can
(and must) do name-lookup normally. */
if (template_keyword_p
&& (!parser->scope
|| (TYPE_P (parser->scope)
&& dependent_type_p (parser->scope))))
return identifier;
}

/* Look up the name. */
decl = cp_parser_lookup_name (parser, identifier,
none_type,
/*is_template=*/false,
/*is_namespace=*/false,
check_dependency_p,
/*ambiguous_decls=*/NULL,
token->location);
decl = maybe_get_template_decl_from_type_decl (decl);

/* If DECL is a template, then the name was a template-name. */
if (TREE_CODE (decl) == TEMPLATE_DECL)
;
else
{
tree fn = NULL_TREE;

/* The standard does not explicitly indicate whether a name that
names a set of overloaded declarations, some of which are
templates, is a template-name. However, such a name should
be a template-name; otherwise, there is no way to form a
template-id for the overloaded templates. */
fns = BASELINK_P (decl) ? BASELINK_FUNCTIONS (decl) : decl;
if (TREE_CODE (fns) == OVERLOAD)
for (fn = fns; fn; fn = OVL_NEXT (fn))
if (TREE_CODE (OVL_CURRENT (fn)) == TEMPLATE_DECL)
break;

if (!fn)
{
/* The name does not name a template. */
cp_parser_error (parser, "expected template-name");
return error_mark_node;
}
}

/* If DECL is dependent, and refers to a function, then just return
its name; we will look it up again during template instantiation. */
if (DECL_FUNCTION_TEMPLATE_P (decl) || !DECL_P (decl))
{
tree scope = CP_DECL_CONTEXT (get_first_fn (decl));
if (TYPE_P (scope) && dependent_type_p (scope))
return identifier;
}

return decl;
}

2、如果没有识别出来为template会如何继续处理

在语法分析的时候,把T::baz<T::bzz识别为一个“小于”二元操作(build_x_binary_op函数生成),然后归结为新的中间结果和后续 '>()'的处理,此时的'>'识别为小于号,而之后正常应该是一个常规表达式,例如可以是(1+2),或者(int)0.1/0.01。
T::baz<T::bzz>();
所以,如果使用下面的形式,编译器竟然不会报错
tsecer@harry: cat -n template.keyword.cpp
1 template<typename T>
2 struct A
3 {
4 int foo()
5 {
6 return T::baz<T::bzz>(1+2);
7 }
8 };
tsecer@harry: gcc -c template.keyword.cpp
tsecer@harry:

gcc中显示小于号左右arg1和arg2的值,可以看到,它们的确被认为是简单的identifier

<scope_ref 0x7ffff0c80780
arg 0 <template_type_parm 0x7ffff0c8d900 T type_0 type_6 VOID
align 8 symtab 0 alias set -1 canonical type 0x7ffff0c8d900
index 0 level 1 orig_level 1
chain <type_decl 0x7ffff0c8d9c0 T>>
arg 1 <identifier_node 0x7ffff0c96a20 bzz
bindings <(nil)>
local bindings <(nil)>>>

 

<scope_ref 0x7ffff0c806c0
arg 0 <template_type_parm 0x7ffff0c8d900 T type_0 type_6 VOID
align 8 symtab 0 alias set -1 canonical type 0x7ffff0c8d900
index 0 level 1 orig_level 1
chain <type_decl 0x7ffff0c8d9c0 T>>
arg 1 <identifier_node 0x7ffff0c969c0 baz
bindings <(nil)>
local bindings <(nil)>>>

 

posted on 2020-03-27 20:34  tsecer  阅读(306)  评论(0编辑  收藏  举报

导航