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

 


推荐阅读
  • 并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
    Java并发编程实践目录并发编程01——ThreadLocal并发编程02——ConcurrentHashMap并发编程03——阻塞队列和生产者-消费者模式并发编程04——闭锁Co ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 利用决策树预测NBA比赛胜负的Python数据挖掘实践
    本文通过使用2013-14赛季NBA赛程与结果数据集以及2013年NBA排名数据,结合《Python数据挖掘入门与实践》一书中的方法,展示如何应用决策树算法进行比赛胜负预测。我们将详细讲解数据预处理、特征工程及模型评估等关键步骤。 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 在尝试使用C# Windows Forms客户端通过SignalR连接到ASP.NET服务器时,遇到了内部服务器错误(500)。本文将详细探讨问题的原因及解决方案。 ... [详细]
  • Python + Pytest 接口自动化测试中 Token 关联登录的实现方法
    本文将深入探讨 Python 和 Pytest 在接口自动化测试中如何实现 Token 关联登录,内容详尽、逻辑清晰,旨在帮助读者掌握这一关键技能。 ... [详细]
  • 由二叉树到贪心算法
    二叉树很重要树是数据结构中的重中之重,尤其以各类二叉树为学习的难点。单就面试而言,在 ... [详细]
  • 深入解析 Android IPC 中的 Messenger 机制
    本文详细介绍了 Android 中基于消息传递的进程间通信(IPC)机制——Messenger。通过实例和源码分析,帮助开发者更好地理解和使用这一高效的通信工具。 ... [详细]
  • CentOS系统安装与配置常见问题及解决方案
    本文详细介绍了在CentOS系统安装过程中遇到的常见问题及其解决方案,包括Vi编辑器的操作、图形界面的安装、网络连接故障排除等。通过本文,读者可以更好地理解和解决这些常见问题。 ... [详细]
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
  • 本文探讨了如何通过预处理器开关选择不同的类实现,并解决在特定情况下遇到的链接器错误。 ... [详细]
  • 在PHP后端开发中遇到一个难题:通过第三方类文件发送短信功能返回的JSON字符串无法解析。本文将探讨可能的原因并提供解决方案。 ... [详细]
  • 为了解决不同服务器间共享图片的需求,我们最初考虑建立一个FTP图片服务器。然而,考虑到项目是一个简单的CMS系统,为了简化流程,团队决定探索七牛云存储的解决方案。本文将详细介绍使用七牛云存储的过程和心得。 ... [详细]
  • Django Token 认证详解与 HTTP 401、403 状态码的区别
    本文详细介绍了如何在 Django 中配置和使用 Token 认证,并解释了 HTTP 401 和 HTTP 403 状态码的区别。通过具体的代码示例,帮助开发者理解认证机制及权限控制。 ... [详细]
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社区 版权所有