首页 > 其他分享 >g++如何判断>>是模板结束还是右移操作符

g++如何判断>>是模板结束还是右移操作符

时间:2024-09-07 16:37:15浏览次数:12  
标签:右移 ++ parser list argument token template cp 模板

intro

在使用模板声明中,有一个经典的问题就是如何区分模板声明中的">>"是右移操作符还是一个模板声明的结束标志。好在新的C++标准削弱了这个很强的限制,而是允许reasonable的、根据上下文对符号进行不同的解析。

C++11 improves the specification of the parser so that multiple right angle brackets will be interpreted as closing the template argument list where it is reasonable. This can be overridden by using parentheses around parameter expressions using the “>”, “>=” or “>>” binary operators

这个问题由来已久,是C++的传统遗留问题,不少使用模板的程序员可能都会踩到这个坑,这个看起来不太合理的现象跟经典的C++编译器实现模型有关:经典的编译器都是由词法分析器和语法分析器两大部分组成,词法分析器负责token识别,当遇到>>时,词法分析器已经直接判定为是一个逻辑右移操作符,并把这个属性传递给语法分析器。这里关键的问题在于”模板“是一个语法概念,词法分析的时候无法感知到语法上下文。

C++ standard

但是这个"reasonable"并不是一个定量的描述,具体而精确的描述还是要看C++标准的说明:

When parsing a template-argument-list, the first non-nested > is taken as the ending delimiter rather than
a greater-than operator. Similarly, the first non-nested >> is treated as two consecutive but distinct > tokens,
the first of which is taken as the end of the template-argument-list and completes the template-id.
[Note 2: The second > token produced by this replacement rule can terminate an enclosing template-id construct or it
can be part of a different construct (e.g., a cast). —end note]

[Example 2:  
 template<int i> class X { /* ... */ };  
 X< 1>2 > x1;  
 X<(1>2)> x2;  
 // syntax error  
 // OK  
 template<class T> class Y { /* ... */ };  
 Y<X<1>> x3;  
 // OK, same as Y<X<1> > x3;  
 Y<X<6>>1>> x4;  
 Y<X<(6>>1)>> x5;  
 —end example]  
 // syntax error  

这个规则描述就简单明了:非嵌套的(non-nested )的>都应该被解析为模板结束符。反过来说,如果想在模板参数中使用逻辑右移操作符就必须包括在括弧中。

可能实现

有了C++的标准,实现就简单很多了:在处理模板声明时,遇到开始引导符(<)之后,开始找对应的结束符(>);但是因为>>也会结束声明,所以在模板声明的时候,应该需要记录当前遇到的启示引导符的层数,如果层数大于1,扫描的时候应该同时也需要关注>>。

但是这种实现看起来比较直观,但是处理起来感觉有些麻烦,因为通常gcc在语法解析的时候都是指定一个期望的结束符。例如,如果遇到了左括号,那么单单期望的就是一个有括号。同样,在遇到模板起始引导符左尖括号之后,单单期望一个右尖括号是最简单的。如果期望多个字符,这个感觉实现上要为这种特殊情况修改接口(即函数参数不是一个字符而是一个字符集,并且由于右移是两个字符,所以单单使用字符串作为参数还有定界的问题。

这里补充说明下放在括弧内为什么会很简单:因为当遇到左括号之后,期望的结束符就暂时变成右括号了,等遇到右括号之后再继续期望右尖括号。

gcc实现

在读取模板argument列表结束符时,不仅判断了常规的>操作符,而且也判断了可能的>>操作符(注意:在括弧内的>不会在这里处理,简单理解就是左括弧会有自己的开始和结束逻辑)。

///@file: gcc\cp\parser.cc
/* Returns TRUE iff the next token is the "," or ">" (or `>>', in
   C++0x) ending a template-argument.  */

static bool
cp_parser_next_token_ends_template_argument_p (cp_parser *parser)
{
  cp_token *token;

  token = cp_lexer_peek_token (parser->lexer);
  return (token->type == CPP_COMMA
          || token->type == CPP_GREATER
          || token->type == CPP_ELLIPSIS
	  || ((cxx_dialect != cxx98) && token->type == CPP_RSHIFT)
	  /* For better diagnostics, treat >>= like that too, that
	     shouldn't appear non-nested in template arguments.  */
	  || token->type == CPP_RSHIFT_EQ);
}

下面的代码也是巧妙的解决了一个token被识别且仅被识别两次的问题,正如注释所说:

change the current token to a `>', but don't consume it

当遇到>>操作符时,会把这个token的类型修改为单个>,但是不消耗这个token(也就是这个token依然在词法分析器的输出中),这样下次还会读取到这个token,只是类型从>>变成了单个>,在第二次处理这个token的时候,因为它已经是一个常规token,所以会被消耗。

这种处理方法的巧妙之处是通过修改token的状态来记录了作为结束符的次数,避免了开始糟糕程序员(例如我)考虑的通过计数来表示模板嵌套层数的问题。


/* Parse a template-argument-list, as well as the trailing ">" (but
   not the opening "<").  See cp_parser_template_argument_list for the
   return value.  */

static tree
cp_parser_enclosed_template_argument_list (cp_parser* parser)
{
  tree arguments;
  tree saved_scope;
  tree saved_qualifying_scope;
  tree saved_object_scope;
  bool saved_greater_than_is_operator_p;

  /* [temp.names]

     When parsing a template-id, the first non-nested `>' is taken as
     the end of the template-argument-list rather than a greater-than
     operator.  */
  saved_greater_than_is_operator_p
    = parser->greater_than_is_operator_p;
  parser->greater_than_is_operator_p = false;
  /* Parsing the argument list may modify SCOPE, so we save it
     here.  */
  saved_scope = parser->scope;
  saved_qualifying_scope = parser->qualifying_scope;
  saved_object_scope = parser->object_scope;
  /* We need to evaluate the template arguments, even though this
     template-id may be nested within a "sizeof".  */
  cp_evaluated ev;
  /* Parse the template-argument-list itself.  */
  if (cp_lexer_next_token_is (parser->lexer, CPP_GREATER)
      || cp_lexer_next_token_is (parser->lexer, CPP_RSHIFT)
      || cp_lexer_next_token_is (parser->lexer, CPP_GREATER_EQ)
      || cp_lexer_next_token_is (parser->lexer, CPP_RSHIFT_EQ))
    {
      arguments = make_tree_vec (0);
      SET_NON_DEFAULT_TEMPLATE_ARGS_COUNT (arguments, 0);
    }
  else
    arguments = cp_parser_template_argument_list (parser);
  /* Look for the `>' that ends the template-argument-list. If we find
     a '>>' instead, it's probably just a typo.  */
  if (cp_lexer_next_token_is (parser->lexer, CPP_RSHIFT))
    {
      if (cxx_dialect != cxx98)
        {
          /* In C++0x, a `>>' in a template argument list or cast
             expression is considered to be two separate `>'
             tokens. So, change the current token to a `>', but don't
             consume it: it will be consumed later when the outer
             template argument list (or cast expression) is parsed.
             Note that this replacement of `>' for `>>' is necessary
             even if we are parsing tentatively: in the tentative
             case, after calling
             cp_parser_enclosed_template_argument_list we will always
             throw away all of the template arguments and the first
             closing `>', either because the template argument list
             was erroneous or because we are replacing those tokens
             with a CPP_TEMPLATE_ID token.  The second `>' (which will
             not have been thrown away) is needed either to close an
             outer template argument list or to complete a new-style
             cast.  */
	  cp_token *token = cp_lexer_peek_token (parser->lexer);
          token->type = CPP_GREATER;
        }
      else if (!saved_greater_than_is_operator_p)
	{
	  /* If we're in a nested template argument list, the '>>' has
	    to be a typo for '> >'. We emit the error message, but we
	    continue parsing and we push a '>' as next token, so that
	    the argument list will be parsed correctly.  Note that the
	    global source location is still on the token before the
	    '>>', so we need to say explicitly where we want it.  */
	  cp_token *token = cp_lexer_peek_token (parser->lexer);
	  gcc_rich_location richloc (token->location);
	  richloc.add_fixit_replace ("> >");
	  error_at (&richloc, "%<>>%> should be %<> >%> "
		    "within a nested template argument list");

	  token->type = CPP_GREATER;
	}
      else
	{
	  /* If this is not a nested template argument list, the '>>'
	    is a typo for '>'. Emit an error message and continue.
	    Same deal about the token location, but here we can get it
	    right by consuming the '>>' before issuing the diagnostic.  */
	  cp_token *token = cp_lexer_consume_token (parser->lexer);
	  error_at (token->location,
		    "spurious %<>>%>, use %<>%> to terminate "
		    "a template argument list");
	}
    }

栗子

右移操作符第一次作为单个>结束了函数模板参数,并在其中变成了一个常规的>操作符,从而错误提示和单独的一个(第4行)>错误提示相同。

tsecer@harry: cat -n right_shift_template_ending.cpp 
     1  template <typename T>
     2  struct A{};
     3  A<int>> a;
     4  >
     5
tsecer@harry: gcc -c right_shift_template_ending.cpp 
right_shift_template_ending.cpp:3:6: error: expected unqualified-id before ‘>’ token
 A<int>> a;
      ^~
right_shift_template_ending.cpp:4:1: error: expected unqualified-id before ‘>’ token
 >
 ^

outro

gcc的实现非常简洁优雅。很多时候,自己实现的功能可能只是“能用”,远达不到优秀的级别,所以使用成熟功能不仅可以避免潜在的错误,而且效率也会更高。

标签:右移,++,parser,list,argument,token,template,cp,模板
From: https://www.cnblogs.com/tsecer/p/18401845

相关文章

  • 设计模式之模板方法模式(三分钟学会一个设计模式)
    模板方法模式(TemplateMethodPattern)也称之为模板模式(TemplatePattern),是设计模式中最简单的模式之一。先来看定义:定义一个操作中算法的骨架(模板),将一些步骤延迟到子类中,模板方法使得子类可以不改变算法的结构即可重新定义算法某些特定的步骤。这个定义还是有一些晦涩,我的理解是......
  • 模板化-上线文件的流程步骤
    1.服务和分支,避免上线服务遗漏,及定义上线的优先级先后顺序。2.apollo配置,检查上线的pro环境的apollo配置,否则项目启动因为缺少配置会启动报错。3.SQLDMS,上线之前先执行脚本,否则会因为缺少字段而导致系统报错。flow系统加上客户和商户的会员号字段需求,上线之前忘记了。ALTERTA......
  • aspose word指定位置插入图片,借助word模板文件中的书签来定位 及Java 获取网络图片
    asposeword指定位置插入图片,借助word模板文件中的书签来定位 及Java 获取网络图片链接:asposeword模板文件生成pdfhttps://www.cnblogs.com/oktokeep/p/16615900.html在Aspose.Words中,您可以使用DocumentBuilder类在指定位置插入图片。以下是一个简单的示例代码,展示如何实现......
  • Qt C++编程 从入门到实践 彭源 清华大学出版社
    第一章程序设计基础1.2.1输入和输出操作iostream叫做标准输入输出流库头文件namespacestd叫做标准命名空间cout、cin叫做标准输出、输入流对象有时候看见std::cout的代码,是因为没有事先声明cout对象是从标准命名空间调用的,::叫做域解析运算符,作用就是指明cout这个对象是......
  • [C++ Daily] 递归锁解决标准锁的典型应用
    递归锁解决标准锁的典型应用先看源码:结果(在A种尝试锁住mutex_时失败,进程等待,死锁无法退出:将std::mutex用std::recursive_mutex替换:结果:解析:std::recursive_mutex允许同一个线程对同一个锁对象进行多次上锁,获得多层所有权.......
  • C++中的字符和字符串
    一:引言1、错误分析请先看一下以下代码#include<iostream>#include<utility>//包含pair和make_pair的定义intmain(){//创建pair对象std::pair<int,std::string>p1(1,"one");//使用make_pair创建pair对象autop2=std::make_pair(2,"t......
  • 迷宫,返回所有路径并排序(C++)(回溯+dfs)
    题意说明:要求给出一个m*n的矩阵迷宫,0代表入口,1代表道路,2代表墙体,3代表出口,要求返回从入口到出口的所有移动路径并按长短从小到大排序。移动路径:就是wasd的移动路径,如一共走三步:下,右,下,则输出:sds。代码如下:#include<iostream>#include<string>#include<vector>#include<alg......
  • 【C++】模板初阶
    【C++】模板初阶1.函数模板(1).函数模板概念(2).函数模板格式(3).函数模板的原理(4).函数模板的实例化2.类模板(1).类模板的定义格式(2).类模板的实例化1.函数模板(1).函数模板概念函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据......
  • C++(clock())
    目录1.clock_t2.clock()2.1函数定义3.示例4.注意事项在C++中,clock_t和clock()是与时间度量和性能测量相关的库函数,主要用于计算程序运行的时间。1.clock_tclock_t是在<ctime>或<time.h>中定义的一个类型,通常用于存储由clock()返回的处理器时间值。这个类型......
  • 【生日视频制作】酒吧一群美女车展模特大屏幕视频改字AE模板修改文字软件生成器教程特
    生日视频制作教程酒吧一群美女车展模特大屏幕视频改字AE模板修改文字特效广软件告生成神器素材祝福玩法AE模板工程怎么如何做的【生日视频制作】酒吧一群美女车展模特大屏幕视频改字AE模板修改文字软件生成器教程特效素材【AE模板】生日视频制作步骤:安装AE软件下载......