C++风格cast的优先级
一、为什么注意到这个问题
之前在使用C风格的cast时候,一个比较烦的地方就是由于转换操作的优先级,导致转换出来的类型需要使用额外的一个括号抱起来,看起来非常臃肿。后来注意到在C++风格的转换符貌似优先级更高,虽然输入量有所增加,但是输入的时候比较流畅,因此看起来也更加流程。
tsecer@harry: cat -n cpp.style.cast.cpp
1 struct S
2 {
3 int x;
4 };
5
6 int foo(void *vp)
7 {
8 return static_cast<const S*>(vp)->x
9 + (S*)(vp)->x;
10 }
tsecer@harry: g++ -c cpp.style.cast.cpp
cpp.style.cast.cpp: 在函数‘int foo(void*)’中:
cpp.style.cast.cpp:9:14: 错误:‘void*’不是一个指向对象的类型
+ (S*)(vp)->x;
^
tsecer@harry:
二、cast的优先级
在C语言中,明确定义了cast的优先级,
优先级 | 运算符 | 描述 | 结合性 |
---|---|---|---|
1 | :: |
作用域解析 | 从左到右 |
2 | a++ a-- |
后缀自增与自减 | |
type() type{} |
函数风格转型 | ||
a() |
函数调用 | ||
a[] |
下标 | ||
. -> |
成员访问 | ||
3 | ++a --a |
前缀自增与自减 | 从右到左 |
+a -a |
一元加与减 | ||
! ~ |
逻辑非和逐位非 | ||
(type) |
C 风格转型 | ||
*a |
间接(解引用) | ||
&a |
取址 | ||
sizeof |
取大小[注 1] | ||
co_await |
await 表达式 (C++20) | ||
new new[] |
动态内存分配 | ||
delete delete[] |
动态内存分配 |
它的优先级和“前缀递增”(++)相同,低于“成员访问”(->)的优先级。但是对于C++风格的转换,“注解”中的说明是
表中并未包括 const_cast、static_cast、dynamic_cast、reinterpret_cast、typeid、sizeof...、noexcept 及 alignof,因为它们决不会有歧义。
但是并没有明确说明它们的优先级。Stack Overflow上对该问题讨论的一个帖子。
三、gcc中对该语法的处理
从该函数的实现可以看到,对于C++风格的cast,只要识别出 const_cast、static_cast、dynamic_cast、reinterpret_cast、这个关键字,后面的流程就是一马平川了,之后必须跟左尖括号/小于号(RT_LESS),类型(typeid),右尖括号/大于号(RT_GREATER)、左括弧(RT_OPEN_PAREN)、表达式(expression)、有括弧(RT_CLOSE_PAREN)。最关键的是,在这些解析完毕之后,直接调用build_dynamic_cast/build_static_cast/build_reinterpret_cast/build_const_cast来生成表达式,从这个意义上来说,C++风格的cast更像是一个原子操作,它们整个语法结构构成了一个基本单位。
gcc-4.9.0\gcc\cp\parser.c
/* Parse a postfix-expression.
postfix-expression:
primary-expression
postfix-expression [ expression ]
postfix-expression ( expression-list [opt] )
simple-type-specifier ( expression-list [opt] )
typename :: [opt] nested-name-specifier identifier
( expression-list [opt] )
typename :: [opt] nested-name-specifier template [opt] template-id
( expression-list [opt] )
postfix-expression . template [opt] id-expression
postfix-expression -> template [opt] id-expression
postfix-expression . pseudo-destructor-name
postfix-expression -> pseudo-destructor-name
postfix-expression ++
postfix-expression --
dynamic_cast < type-id > ( expression )
static_cast < type-id > ( expression )
reinterpret_cast < type-id > ( expression )
const_cast < type-id > ( expression )
typeid ( expression )
typeid ( type-id )
GNU Extension:
postfix-expression:
( type-id ) { initializer-list , [opt] }
This extension is a GNU version of the C99 compound-literal
construct. (The C99 grammar uses `type-name' instead of `type-id',
but they are essentially the same concept.)
If ADDRESS_P is true, the postfix expression is the operand of the
`&' operator. CAST_P is true if this expression is the target of a
cast.
If MEMBER_ACCESS_ONLY_P, we only allow postfix expressions that are
class member access expressions [expr.ref].
Returns a representation of the expression. */
static tree
cp_parser_postfix_expression (cp_parser *parser, bool address_p, bool cast_p,
bool member_access_only_p, bool decltype_p,
cp_id_kind * pidk_return)
{
cp_token *token;
location_t loc;
enum rid keyword;
cp_id_kind idk = CP_ID_KIND_NONE;
tree postfix_expression = NULL_TREE;
bool is_member_access = false;
int saved_in_statement = -1;
/* Peek at the next token. */
token = cp_lexer_peek_token (parser->lexer);
loc = token->location;
/* Some of the productions are determined by keywords. */
keyword = token->keyword;
switch (keyword)
{
case RID_DYNCAST:
case RID_STATCAST:
case RID_REINTCAST:
case RID_CONSTCAST:
{
tree type;
tree expression;
const char *saved_message;
bool saved_in_type_id_in_expr_p;
/* All of these can be handled in the same way from the point
of view of parsing. Begin by consuming the token
identifying the cast. */
cp_lexer_consume_token (parser->lexer);
/* New types cannot be defined in the cast. */
saved_message = parser->type_definition_forbidden_message;
parser->type_definition_forbidden_message
= G_("types may not be defined in casts");
/* Look for the opening `<'. */
cp_parser_require (parser, CPP_LESS, RT_LESS);
/* Parse the type to which we are casting. */
saved_in_type_id_in_expr_p = parser->in_type_id_in_expr_p;
parser->in_type_id_in_expr_p = true;
type = cp_parser_type_id (parser);
parser->in_type_id_in_expr_p = saved_in_type_id_in_expr_p;
/* Look for the closing `>'. */
cp_parser_require (parser, CPP_GREATER, RT_GREATER);
/* Restore the old message. */
parser->type_definition_forbidden_message = saved_message;
bool saved_greater_than_is_operator_p
= parser->greater_than_is_operator_p;
parser->greater_than_is_operator_p = true;
/* And the expression which is being cast. */
cp_parser_require (parser, CPP_OPEN_PAREN, RT_OPEN_PAREN);
expression = cp_parser_expression (parser, /*cast_p=*/true, & idk);
cp_parser_require (parser, CPP_CLOSE_PAREN, RT_CLOSE_PAREN);
parser->greater_than_is_operator_p
= saved_greater_than_is_operator_p;
/* Only type conversions to integral or enumeration types
can be used in constant-expressions. */
if (!cast_valid_in_integral_constant_expression_p (type)
&& cp_parser_non_integral_constant_expression (parser, NIC_CAST))
return error_mark_node;
switch (keyword)
{
case RID_DYNCAST:
postfix_expression
= build_dynamic_cast (type, expression, tf_warning_or_error);
break;
case RID_STATCAST:
postfix_expression
= build_static_cast (type, expression, tf_warning_or_error);
break;
case RID_REINTCAST:
postfix_expression
= build_reinterpret_cast (type, expression,
tf_warning_or_error);
break;
case RID_CONSTCAST:
postfix_expression
= build_const_cast (type, expression, tf_warning_or_error);
break;
default:
gcc_unreachable ();
}
}
break;
……
}