C++11中部分新语法说明
一、变长模版参数(variadic template paramter)
1、语法说明
gcc源代码中对于该语法的解析是在gcc-4.8.2\gcc\cp\parser.c文件中完成,同样是"...",如何区分哪些是pack,哪些是expansion呢?从代码(的注释)上来看,只有在模版声明template<typename...> 这种位置的声明才是pack,其它地方的都是expansion,所以必须在之前已经出现过(被声明过)。
2、gcc中简单的语法解析
gcc-4.8.2\gcc\cp\pt.c
tree
make_pack_expansion (tree arg)
{
……
/* Build the PACK_EXPANSION_* node. */
result = for_types
? cxx_make_type (TYPE_PACK_EXPANSION)
: make_node (EXPR_PACK_EXPANSION);
……
}
这个地方起始就是把单个变量展开为一个vector列表。直观上说,一个变量的展开就好像是进行从语法上展开为逗号分割的表达式列表。其实,通常我们见到的逗号分割列表就是为了组成一个vector结构,而展开是另一种形式的打包,两者殊路同归。
gcc-4.8.2\gcc\cp\pt.c
/* Substitute ARGS into T, which is an pack expansion
(i.e. TYPE_PACK_EXPANSION or EXPR_PACK_EXPANSION). Returns a
TREE_VEC with the substituted arguments, a PACK_EXPANSION_* node
(if only a partial substitution could be performed) or
ERROR_MARK_NODE if there was an error. */
tree
tsubst_pack_expansion (tree t, tree args, tsubst_flags_t complain,
tree in_decl)
{
……
/* For each argument in each argument pack, substitute into the
pattern. */
result = make_tree_vec (len);
for (i = 0; i < len; ++i)
{
t = gen_elem_of_pack_expansion_instantiation (pattern, packs,
i,
args, complain,
in_decl);
TREE_VEC_ELT (result, i) = t;
if (t == error_mark_node)
{
result = error_mark_node;
break;
}
}
……
}
3、典型的使用场景
网络上对于这种语法引用场景比较多的都是使用了对任意多参数求和例子,
template<typename T>
T adder(T v) {
return v;
}
template<typename T, typename... Args>
T adder(T first, Args... args) {
return first + adder(args...);
}
4、另一个相似的例子是让任意个数参数连接为单个字符串
tsecer@harry: cat vartmpl.cpp
#include<sstream>
#include<iostream>
struct A
{
std::stringstream m_s;
template<typename T>
void accum(T t)
{
m_s<<t<<"|";
}
template<typename TF, typename... TS>
void accum(TF f, TS... ts)
{
accum(f);
accum(ts...);
}
};
int main()
{
A a;
a.accum(1, 2, 3, "sum", 1 + 2 + 3, "end", 1);
std::cout<<a.m_s.str()<<std::endl;
}
tsecer@harry: g++ vartmpl.cpp -std=gnu++11
tsecer@harry: ./a.out
1|2|3|sum|6|end|1|
tsecer@harry:
二、lambda表达式
1、语法结构
这个语法还是很有意义的,本身结合了 中括号、小括号、大括号三种括号结构。看起来诡异的语法结构起始还是为了便于编译器进行语法解析,例如对于g++对于C++的实现,它是通过“手动”构造的语法分析器。对于[]开始的primary expression,可以方便的确定它就是一个lambda表达式。例如在
gcc-4.8.2\gcc\cp\parser.c
static tree
cp_parser_primary_expression (cp_parser *parser,
bool address_p,
bool cast_p,
bool template_arg_p,
bool decltype_p,
cp_id_kind *idk)
{
……
case CPP_OPEN_SQUARE:
if (c_dialect_objc ())
/* We have an Objective-C++ message. */
return cp_parser_objc_expression (parser);
{
tree lam = cp_parser_lambda_expression (parser);
/* Don't warn about a failed tentative parse. */
if (cp_parser_error_occurred (parser))
return error_mark_node;
maybe_warn_cpp0x (CPP0X_LAMBDA_EXPR);
return lam;
}
……
这里判断如果主表达式是以"[]"开始,就可以立即判断出它是一个lambda表达式。这里也可以理解为什么把返回值放在参数之后的原因,其实同样是为了避免和普通的变量声明分开。否则当遇到一个int开始的表达式时,无法立即确定它是一个变量声明还是一个lambda表达式;反而隐藏在 小括号 的后面解析起来就更加方便。
2、和python中lambda表达式的比较
在python中,lambda的开始就是通过lambda关键字引导,它的语法定义为Python-2.7.13\Grammar\Grammar.txt
old_lambdef: 'lambda' [varargslist] ':' old_test
这个语法就是使用lambda表达式引入lambda语法。但是这个语法有一个问题,就是不能对lambda可以访问的外部变量进行严格的控制,而C++作为一个相对更加底层的语言来说,为了对lambda表达式之外的变量可见性进行控制,引入"[]"这样的包含符号可能更加合适。
3、排序
tsecer@harry: cat c++lambda.cpp
#include <algorithm>
#include <iostream>
bool cmp(const int lhs, const int rhs)
{
return lhs > rhs;
}
int main()
{
int arr[] = {2, 4, 6, 8, 9, 0, 10};
std::sort(arr + 0, arr + sizeof(arr)/ sizeof(arr[0]), [](int lhs, int rhs) -> bool { return lhs > rhs;} );
std::for_each(arr, arr + sizeof(arr)/sizeof(arr[0]), [](int x) {std::cout<<x<<std::endl;});
return 0;
}
tsecer@harry: g++ c++lambda.cpp -std=c++11
tsecer@harry: ./a.out
10
9
8
6
4
2
0
三、基于区间的for循环
1、编译器对于该语法的解析
函数cp_parser_for_init_statement返回值表示是否是一个range类型的for循环
gcc-4.8.2\gcc\cp\parser.c
static tree
cp_parser_for (cp_parser *parser)
{
tree init, scope, decl;
bool is_range_for;
/* Begin the for-statement. */
scope = begin_for_scope (&init);
/* Parse the initialization. */
is_range_for = cp_parser_for_init_statement (parser, &decl);
if (is_range_for)
return cp_parser_range_for (parser, scope, init, decl);
else
return cp_parser_c_for (parser, scope, init);
}
判断的如果初始表达式后面是冒号,则认为是一个基于range的for循环
static bool
cp_parser_for_init_statement (cp_parser* parser, tree *decl)
{
……
if (cp_lexer_next_token_is (parser->lexer, CPP_COLON))
{
/* It is a range-for, consume the ':' */
cp_lexer_consume_token (parser->lexer);
is_range_for = true;
if (cxx_dialect < cxx0x)
{
error_at (cp_lexer_peek_token (parser->lexer)->location,
"range-based %<for%> loops are not allowed "
"in C++98 mode");
*decl = error_mark_node;
}
}
/* Converts a range-based for-statement into a normal
for-statement, as per the definition.
for (RANGE_DECL : RANGE_EXPR)
BLOCK
should be equivalent to:
{
auto &&__range = RANGE_EXPR;
for (auto __begin = BEGIN_EXPR, end = END_EXPR;
__begin != __end;
++__begin)
{
RANGE_DECL = *__begin;
BLOCK
}
}
……
}
函数注释中关于这个for循环的注释
If RANGE_EXPR is an array:
BEGIN_EXPR = __range
END_EXPR = __range + ARRAY_SIZE(__range)
Else if RANGE_EXPR has a member 'begin' or 'end':
BEGIN_EXPR = __range.begin()
END_EXPR = __range.end()
Else:
BEGIN_EXPR = begin(__range)
END_EXPR = end(__range);
If __range has a member 'begin' but not 'end', or vice versa, we must
still use the second alternative (it will surely fail, however).
When calling begin()/end() in the third alternative we must use
argument dependent lookup, but always considering 'std' as an associated
namespace. */
tree
cp_convert_range_for (tree statement, tree range_decl, tree range_expr)
{
……
}
而具体的对于注释代码的实现位于函数gcc-4.8.2\gcc\cp\parser.c,结合之前的注释,下面的代码即使只看注释和函数名应该也可以理解它们的意思。
/* Solves BEGIN_EXPR and END_EXPR as described in cp_convert_range_for.
We need to solve both at the same time because the method used
depends on the existence of members begin or end.
Returns the type deduced for the iterator expression. */
static tree
cp_parser_perform_range_for_lookup (tree range, tree *begin, tree *end)
{
……
if (TREE_CODE (TREE_TYPE (range)) == ARRAY_TYPE)
{
/* If RANGE is an array, we will use pointer arithmetic. */
*begin = range;
*end = build_binary_op (input_location, PLUS_EXPR,
range,
array_type_nelts_top (TREE_TYPE (range)),
0);
return build_pointer_type (TREE_TYPE (TREE_TYPE (range)));
}
else
{
/* If it is not an array, we must do a bit of magic. */
tree id_begin, id_end;
tree member_begin, member_end;
*begin = *end = error_mark_node;
id_begin = get_identifier ("begin");
id_end = get_identifier ("end");
member_begin = lookup_member (TREE_TYPE (range), id_begin,
/*protect=*/2, /*want_type=*/false,
tf_warning_or_error);
member_end = lookup_member (TREE_TYPE (range), id_end,
/*protect=*/2, /*want_type=*/false,
tf_warning_or_error);
if (member_begin != NULL_TREE || member_end != NULL_TREE)
{
/* Use the member functions. */
if (member_begin != NULL_TREE)
*begin = cp_parser_range_for_member_function (range, id_begin);
else
error ("range-based %<for%> expression of type %qT has an "
"%<end%> member but not a %<begin%>", TREE_TYPE (range));
if (member_end != NULL_TREE)
*end = cp_parser_range_for_member_function (range, id_end);
else
error ("range-based %<for%> expression of type %qT has a "
"%<begin%> member but not an %<end%>", TREE_TYPE (range));
}
else
{
/* Use global functions with ADL. */
vec<tree, va_gc> *vec;
vec = make_tree_vector ();
vec_safe_push (vec, range);
member_begin = perform_koenig_lookup (id_begin, vec,
/*include_std=*/true,
tf_warning_or_error);
*begin = finish_call_expr (member_begin, &vec, false, true,
tf_warning_or_error);
member_end = perform_koenig_lookup (id_end, vec,
/*include_std=*/true,
tf_warning_or_error);
*end = finish_call_expr (member_end, &vec, false, true,
tf_warning_or_error);
release_tree_vector (vec);
}
……
}
2、写个例子测试下
例如,下面的报错是组后一种报错,就是要查找可用的begin和end函数
tsecer@harry: cat range.for.cpp
struct A
{
};
int main()
{
A a;
for (int i: a)
{}
}
tsecer@harry: g++ -std=c++11 range.for.cpp -c
range.for.cpp: In function ‘int main()’:
range.for.cpp:8:13: error: ‘begin’ was not declared in this scope
for (int i: a)
^
range.for.cpp:8:13: error: ‘end’ was not declared in this scope
tsecer@harry: