热门标签 | 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)>>>

 


推荐阅读
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文探讨了 Objective-C 中的一些重要语法特性,包括 goto 语句、块(block)的使用、访问修饰符以及属性管理等。通过实例代码和详细解释,帮助开发者更好地理解和应用这些特性。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 不确定性|放入_华为机试题 HJ9提取不重复的整数
    不确定性|放入_华为机试题 HJ9提取不重复的整数 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • MongoDB集群配置:副本集与分片详解
    本文详细介绍了如何在MongoDB中配置副本集(Replica Sets)和分片(Sharding),并提供了具体的步骤和命令,帮助读者理解并实现高可用性和水平扩展的MongoDB集群。 ... [详细]
  • 毕业设计:基于机器学习与深度学习的垃圾邮件(短信)分类算法实现
    本文详细介绍了如何使用机器学习和深度学习技术对垃圾邮件和短信进行分类。内容涵盖从数据集介绍、预处理、特征提取到模型训练与评估的完整流程,并提供了具体的代码示例和实验结果。 ... [详细]
  • 本文深入探讨了HTTP请求和响应对象的使用,详细介绍了如何通过响应对象向客户端发送数据、处理中文乱码问题以及常见的HTTP状态码。此外,还涵盖了文件下载、请求重定向、请求转发等高级功能。 ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • 本文详细介绍了优化DB2数据库性能的多种方法,涵盖统计信息更新、缓冲池调整、日志缓冲区配置、应用程序堆大小设置、排序堆参数调整、代理程序管理、锁机制优化、活动应用程序限制、页清除程序配置、I/O服务器数量设定以及编入组提交数调整等方面。通过这些技术手段,可以显著提升数据库的运行效率和响应速度。 ... [详细]
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社区 版权所有