热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

为什么_为什么在需要使用‘templateasadisambiguator’

本文由编程笔记#小编为大家整理,主要介绍了为什么在需要使用‘templateasadisambiguator’相关的知识,希望对你有一定的参考价值。
本文由编程笔记#小编为大家整理,主要介绍了为什么在需要使用‘template as a disambiguator’相关的知识,希望对你有一定的参考价值。



一、为什么用这个标题

标题中的Chinglish并不是为了装逼,而是为了更加原汁原味的表达这个问题的出现场景,这个说法来自gcc的提示:
gcc-4.4.7gcccpparser.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% (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::template get();
的template关键字。

tsecer@harry: cat -n mytuple.cpp
1 template struct tupleimp;
2 struct callbase{};
3 struct returnthis{};
4
5 template
6 struct chose
7 {
8 typedef callbase TYPE;
9 };
10
11 template<>
12 struct chose
13 {
14 typedef returnthis TYPE;
15 };
16
17 template
18 struct tupleimp
19 {
20 };
21
22 template
23 struct tupleimp:public tupleimp
24 {
25 tupleimp(HEAD h, typelist... args):m_h(h), tupleimp(args...){}
26 HEAD m_h;
27
28 template
29 T get( ){
30 typename chose::TYPE t;
31 return fork(t);
32 }
33
34 template
35 T fork(callbase c) {
36
37 return tupleimp::template get();
38 }
39
40 template
41 T fork(returnthis c) {
42 return m_h;
43 }
44
45
46 };
47 template
48 int foo()
49 {
50 return 0;
51 }
52 int x = foo();
53 template
54 struct tuple: public tupleimp<0, typelist...>
55 {
56 tuple(typelist... args):tupleimp<0, typelist...>(args...){}
57 };
58
59 template
60 T get(TUPLE &t)
61 {
62 return t.get();
63 }
64
65 tuple 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
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::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后面的名字和解析为一个整体的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 X* alloc();
template static X* adjust();
};
template void f(T* p) {
T* p1 = p->alloc<200>(); // ill-formed: T* p2 = p->template alloc<200>(); // OK: T::adjust<100>(); // ill-formed:
T::template adjust<100>(); // OK: }
—end example ]


六、例子

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


1、名称的解析

cp_parser_qualifying_entity==>>cp_parser_class_name==>>cp_parser_template_id==>>cp_parser_template_name
在解析T::template bar()的时候,函数从
"
if (start)
cp_lexer_purge_tokens_after (parser->lexer, start);
if (is_identifier)
*is_identifier = true;
return identifier;
"
这里返回。
当执行T::baz()的时候,此处没有返回,继续往下面执行,返回的节点为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 struct S { S(); };
template S::S();

correctly. We would treat `S‘ as a template -- if it were `S
-- 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 void f(T::X);

we want to treat "X" 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()‘的处理,此时的‘>‘识别为小于号,而之后正常应该是一个常规表达式,例如可以是(1+2),或者(int)0.1/0.01。
T::baz();
所以,如果使用下面的形式,编译器竟然不会报错
tsecer@harry: cat -n template.keyword.cpp
1 template
2 struct A
3 {
4 int foo()
5 {
6 return T::baz(1+2);
7 }
8 };
tsecer@harry: gcc -c template.keyword.cpp
tsecer@harry:

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

arg 0 align 8 symtab 0 alias set -1 canonical type 0x7ffff0c8d900
index 0 level 1 orig_level 1
chain >
arg 1
bindings <(nil)>
local bindings <(nil)>>>

 

arg 0 align 8 symtab 0 alias set -1 canonical type 0x7ffff0c8d900
index 0 level 1 orig_level 1
chain >
arg 1
bindings <(nil)>
local bindings <(nil)>>>

 


推荐阅读
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 本文介绍如何在 Android 中通过代码模拟用户的点击和滑动操作,包括参数说明、事件生成及处理逻辑。详细解析了视图(View)对象、坐标偏移量以及不同类型的滑动方式。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 本文详细介绍了Java中org.neo4j.helpers.collection.Iterators.single()方法的功能、使用场景及代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • 本文基于刘洪波老师的《英文词根词缀精讲》,深入探讨了多个重要词根词缀的起源及其相关词汇,帮助读者更好地理解和记忆英语单词。 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 本文详细介绍了 GWT 中 PopupPanel 类的 onKeyDownPreview 方法,提供了多个代码示例及应用场景,帮助开发者更好地理解和使用该方法。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
author-avatar
拐久了_618
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有