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

 


推荐阅读
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
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社区 版权所有