Commit 5d80a306 by Douglas Gregor Committed by Doug Gregor

re PR c++/20599 (variadic template support)

2007-03-09  Douglas Gregor  <doug.gregor@gmail.com>

	PR c++/20599
	* typeck.c (check_return_expr): Check for bare parameter packs.
	(comptypes): Compare template parameter packs and
	type pack expansions.
	* decl.c (grokdeclarator): Deal with the declaration of function
	parameter packs.
	(grokparms): Verify that the (optional) function parameter pack is
	at the end of the parameter list.
	(xref_basetypes): Handle pack expansions in the base class.
	(cp_tree_node_structure): Handle ARGUMENT_PACK_SELECT.
	* cp-tree.def (TYPE_ARGUMENT_PACK): New.
	(NONTYPE_ARGUMENT_PACK): New.
	(TYPE_PACK_EXPANSION): New.
	(EXPR_PACK_EXPANSION): New.
	(ARGUMENT_PACK_SELECT): New.
	* cp-objcp-common.c (cp_tree_size): Compute size of
	(NON)TYPE_ARGUMENT_PACK, (TYPE|EXPR)_PACK_EXPANSION, and
	ARGUMENT_PACK_SELECT.
	* error.c (dump_template_argument): Print template argument packs.
	(dump_template_argument_list): Ditto.
	(dump_template_parameter): Dump `...' for template type parameter
	packs.
	(dump_type): Dump TYPE_PACK_EXPANSION nodes.
	(dump_parameters): Print function parameter packs.
	(dump_template_parms): Print template argument packs.
	(dump_expr): Dump EXPR_PACK_EXPANSION nodes.
	(maybe_warn_variadic_templates): New.
	* operators.def: Add ellipsis operator for EXPR_PACK_EXPANSION.
	* tree.c (cp_walk_subtrees): Walk BASELINK, TYPE_ARGUMENT_PACK,
	NONTYPE_ARGUMENT_PACK, TYPE_PACK_EXPANSION, EXPR_PACK_EXPANSION,
	CAST_EXPR. 
	* mangle.c (write_type): Mangle TYPE_PACK_EXPANSION.
	(write_template_arg): Write argument packs as separate arguments.
	* cp-tree.h (struct template_parm_index_s): Add flag that
	indicates that the template parameter is actually a parameter
	pack.
	(struct tree_argument_pack_select): New.
	(enum cp_tree_node_structure_enum): Add TS_CP_ARGUMENT_PACK_SELECT.
	(union lang_tree_node): Add argument_pack_select.
	(FUNCTION_PARAMETER_PACK_P): New.
	(PACK_EXPANSION_P): New.
	(PACK_EXPANSION_PATTERN): New.
	(SET_PACK_EXPANSION_PATTERN): New.
	(PACK_EXPANSION_PARAMETER_PACKS): New.
	(ARGUMENT_PACK_P): New.
	(ARGUMENT_PACK_ARGS): New.
	(SET_ARGUMENT_PACK_ARGS): New.
	(ARGUMENT_PACK_INCOMPLETE_P): New.
	(ARGUMENT_PACK_EXPLICIT_ARGS): New.
	(TEMPLATE_PARM_PARAMETER_PACK): New.
	(TEMPLATE_TYPE_PARAMETER_PACK): New.
	(ARGUMENT_PACK_SELECT_FROM_PACK): New.
	(ARGUMENT_PACK_SELECT_INDEX): New.
	(ARGUMENT_PACK_SELECT_ARG): New.
	(struct cp_declarator): Add parameter_pack_p flag.
	(maybe_warn_variadic_templates): Declare.
	(process_template_parm): Add bool parameter IS_PARAMETER_PACK, to
	indicate a template parameter pack. 
	(uses_parameter_packs): Declare.
	(template_parameter_pack_p): Declare.
	(template_parms_variadic_p): Declare.
	(make_pack_expansion): Declare.
	(check_for_bare_parameter_packs): Declare.
	* cxx-pretty-print.c (pp_cxx_unary_expression): Print
	sizeof... expressions. 
	(pp_cxx_expression): Print pack expansions and non-type argument
	packs.
	(pp_cxx_exception_specification): Print pack expansions. 
	(pp_cxx_direct_declarator): Print ellipsis for parameter packs.
	(pp_cxx_ctor_initializer): Print pack expansions.
	(pp_cxx_type_id): Print pack expansions.
	(pp_cxx_template_argument_list): Print argument packs.
	(pp_cxx_template_parameter): Print ellipsis for template parameter
	packs.
	* pt.c (comp_template_parms): Compare template parameter packs.
	(template_parameter_pack_p): New.
	(template_parms_variadic_p): New.
	(template_args_variadic_p): New.
	(make_ith_pack_parameter_name): New.
	(struct find_parameter_pack_data): New.
	(find_parameter_packs_r): New.
	(uses_parameter_packs): New.
	(make_pack_expansion): New.
	(check_for_bare_parameter_packs): New.
	(expand_template_argument_pack): New.
	(reduce_template_parm_level): Propagate parameter pack flag.
	(process_template_parm): Add is_parameter_pack parameter to state
	when the parameter is actually a parameter pack. Create template
	parameter packs when is_parameter_pack is true.
	(current_template_args): The argument for a template parameter
	pack is an argument pack containing a single pack expansion.
	(process_partial_specialization): When checking that non-type
	argument expressions do not involve template parameters, loop over
	the arguments in argument packs separately.
	(push_template_decl_real): Check that the type of the declaration
	does not have any bare parameter packs. Check that primary
	templates have no more than one parameter pack, and that it comes
	at the end of the template parameter list.
	(convert_template_argument): Handle coercions for pack expansion
	expressions by coercing the pattern then rebuilding the expansion.
	(coerce_template_parms): When coercing the arguments for a
	variadic template, pack "extra" arguments into an argument pack.
	(coerce_template_template_parms): Cannot coerce between parameter
	packs and non-pack parameters.
	(template_args_equal): Compare PACK_EXPANSION_P expressions.
	(comp_template_args): Expand all template arguments packs before
	comparing template argument lists.
	(mangle_class_name_for_template): Make argument packs as separate
	template arguments.
	(for_each_template_parm_r): No need to handle BASELINK. 
	(instantiate_class_template): Handle pack expansions in the base
	class list.
	(tsubst_pack_expansion): New.
	(tsubst_template_args): Handle substitutions of argument packs and
	pack expansion into template argument lists.
	(tsubst_decl): Expand function parameter packs into separate
	function parameters.
	(tsubst_arg_types): Expand a type pack expansion into separate
	argument types.
	(tsubst_exception_specification): Handle pack expansions in
	exception specifiers.
	(tsubst): See through ARGUMENT_PACK_SELECT arguments when 
	replacing a template parameter with its argument. If we encounter
	a substitution for an argument pack, just return the parameter
	itself. 
	(tsubst_copy): sizeof(X...) returns the number of elements in
	parameter pack X.  See through ARGUMENT_PACK_SELECT when the
	PARM_DECL is a parameter pack.
	(tsubst_expr): Expression pack expansions and argument packs
	cannot show up here; they will all be handled through function
	calls, sizeof, and template argument lists.
	(tsubst_copy_and_build): sizeof(X...) returns the number of
	elements in parameter pack X.  Handle pack expansions in TREE_LIST
	and CONSTRUCTOR nodes.
	(fn_type_unification): Handle "incomplete" explicit template
	argument lists that specify some of the arguments for a template
	parameter pack.
	(type_unification_real): Unify arguments against pack expansions.
	(template_parm_level_and_index): New, helper function.
	(unify_pack_expansion): New.
	(unify): Unify argument packs on an argument-by-argument basis,
	handling variadic argument packs as well.
	(more_specialized_fn): Handle unification of function parameter
	packs. All things being equal, prefer non-variadic function
	templates to variadic function templates.
	(more_specialized_class): Prefer the variadic class template
	partial specialization that binds fewer arguments to a parameter
	pack.
	(regenerate_decl_from_template): Expand function parameter packs
	into separate parameters.
	(instantiate_decl): Ditto.
	(tsubst_initializer_list): Handle pack expansions for base-class
	initializers.
	(dependent_type_p_r): Determine dependent types in argument packs
	and pack expansions.
	(value_dependent_expression_p): Determine value-dependence of
	non-type argument packs.
	(dependent_template_arg_p): Handle argument packs.
	* semantics.c (finish_cond): Check for bare parameter packs.
	(finish_expr_stmt): Ditto.
	(finish_for_expr): Ditto.
	(finish_switch_cond): Ditto.
	(finish_mem_initializers): Ditto.
	* name-lookup.c (arg_assoc_type): Handle pack expansions and
	argument packs.
	* decl2.c (cp_build_parm_decl): Mark function parameter packs.
	* parser.c (make_declarator): Declarator is not an expansion.
	(make_pointer_declarator): Transfer parameter pack flag to outer
	declarator.
	(make_reference_declarator): Ditto.
	(make_ptrmem_declarator): Ditto.
	(make_call_declarator): Ditto.
	(make_array_declarator): Ditto.
	(cp_parser_postfix_expression): Allow pack expansion expressions
	in the argument list for a call expression.
	(cp_parser_parenthesized_expression_list): Add new parameter
	ALLOW_EXPANSION_P. When true, parse the ellipsis to mean "expand
	into separate arguments."
	(cp_parser_new_placement): Allow pack expansion expressions.
	(cp_parser_new_initializer): Ditto.
	(cp_parser_mem_initializer_list): Allow ellipsis to create a
	base-class initializer expansion.
	(cp_parser_mem_initializer): Ditto.
	(cp_parser_template_parameter_list): Keep track of whether the
	template parameter is a template parameter pack.
	(cp_parser_template_parameter): Parse the ellipsis to indicate a
	template parameter pack.
	(cp_parser_type_parameter): Ditto.
	(cp_parser_template_argument_list): Parse the ellipsis to indicate
	a pack expansion.
	(cp_parser_direct_declarator): Parse the ellipsis to indicate that
	this declarator is a parameter pack.
	(cp_parser_parameter_declaration): The ellipsis does not end the
	parameter declaration, because it might be a parameter pack. Parse
	the ellipsis to indicate a parameter pack.
	(cp_parser_initializer): Allow pack expansions.
	(cp_parser_initializer_list): Allow ellipsis to create an
	initializer expansion.
	(cp_parser_base_clause): Allow ellipsis to create a base specifier
	expansion.
	(cp_parser_type_id_list): Allow ellipsis to create an exception
	specifier expansion.
	(cp_parser_attribute_list): Don't allow pack expansions.
	(cp_parser_functional_cast): Allow pack expansions.
	(cp_parser_sizeof_operand): Allow ellipsis following "sizeof" to
	compute the length of a parameter pack.
	(cp_parser_next_token_ends_template_argument_p): An ellipsis can
	end a template argument.
	* tree.c (cp_walk_subtrees): Walk BASELINK, TYPE_ARGUMENT_PACK,
	NONTYPE_ARGUMENT_PACK, TYPE_PACK_EXPANSION, EXPR_PACK_EXPANSION,
	CAST_EXPR.

From-SVN: r122788
parent 706ca88e
......@@ -124,6 +124,17 @@ cp_tree_size (enum tree_code code)
case DEFAULT_ARG: return sizeof (struct tree_default_arg);
case OVERLOAD: return sizeof (struct tree_overload);
case STATIC_ASSERT: return sizeof (struct tree_static_assert);
case TYPE_ARGUMENT_PACK:
case TYPE_PACK_EXPANSION:
return sizeof (struct tree_common);
case NONTYPE_ARGUMENT_PACK:
case EXPR_PACK_EXPANSION:
return sizeof (struct tree_exp);
case ARGUMENT_PACK_SELECT:
return sizeof (struct tree_argument_pack_select);
default:
gcc_unreachable ();
}
......
......@@ -352,6 +352,76 @@ DEFTREECODE (UNARY_PLUS_EXPR, "unary_plus_expr", tcc_unary, 1)
literal) to be displayed if the condition fails to hold. */
DEFTREECODE (STATIC_ASSERT, "static_assert", tcc_exceptional, 0)
/* Represents an argument pack of types (or templates). An argument
pack stores zero or more arguments that will be used to instantiate
a parameter pack.
ARGUMENT_PACK_ARGS retrieves the arguments stored in the argument
pack.
Example:
template<typename... Values>
class tuple { ... };
tuple<int, float, double> t;
Values is a (template) parameter pack. When tuple<int, float,
double> is instantiated, the Values parameter pack is instantiated
with the argment pack <int, float, double>. ARGUMENT_PACK_ARGS will
be a TREE_VEC containing int, float, and double. */
DEFTREECODE (TYPE_ARGUMENT_PACK, "type_argument_pack", tcc_type, 0)
/* Represents an argument pack of values, which can be used either for
non-type template arguments or function call arguments.
NONTYPE_ARGUMENT_PACK plays precisely the same role as
TYPE_ARGUMENT_PACK, but will be used for packing non-type template
arguments (e.g., "int... Dimensions") or function arguments ("const
Args&... args"). */
DEFTREECODE (NONTYPE_ARGUMENT_PACK, "nontype_argument_pack", tcc_expression, 1)
/* Represents a type expression that will be expanded into a list of
types when instantiated with one or more argument packs.
PACK_EXPANSION_PATTERN retrieves the expansion pattern. This is
the type or expression that we will substitute into with each
argument in an argument pack.
SET_PACK_EXPANSION_PATTERN sets the expansion pattern.
PACK_EXPANSION_PARAMETER_PACKS contains a TREE_LIST of the parameter
packs that are used in this pack expansion.
Example:
template<typename... Values>
struct tied : tuple<Values&...> {
// ...
};
The derivation from tuple contains a TYPE_PACK_EXPANSION for the
template arguments. Its EXPR_PACK_EXPANSION is "Values&" and its
PACK_EXPANSION_PARAMETER_PACKS will contain "Values". */
DEFTREECODE (TYPE_PACK_EXPANSION, "type_pack_expansion", tcc_type, 0)
/* Represents an expression that will be expanded into a list of
expressions when instantiated with one or more argument packs.
EXPR_PACK_EXPANSION plays precisely the same role as TYPE_PACK_EXPANSION,
but will be used for expressions. */
DEFTREECODE (EXPR_PACK_EXPANSION, "expr_pack_expansion", tcc_expression, 1)
/* Selects the Ith parameter out of an argument pack. This node will
be used when instantiating pack expansions; see
tsubst_pack_expansion.
ARGUMENT_PACK_SELECT_FROM_PACK contains the *_ARGUMENT_PACK node
from which the argument will be selected.
ARGUMENT_PACK_SELECT_INDEX contains the index into the argument
pack that will be returned by this ARGUMENT_PACK_SELECT node. The
index is a machine integer. */
DEFTREECODE (ARGUMENT_PACK_SELECT, "argument_pack_select", tcc_exceptional, 0)
/*
Local variables:
mode:c
......
......@@ -103,6 +103,7 @@ struct diagnostic_context;
1: C_TYPEDEF_EXPLICITLY_SIGNED (in TYPE_DECL).
DECL_TEMPLATE_INSTANTIATED (in a VAR_DECL or a FUNCTION_DECL)
DECL_MEMBER_TEMPLATE_P (in TEMPLATE_DECL)
FUNCTION_PARAMETER_PACK_P (in PARM_DECL)
2: DECL_THIS_EXTERN (in VAR_DECL or FUNCTION_DECL).
DECL_IMPLICIT_TYPEDEF_P (in a TYPE_DECL)
3: DECL_IN_AGGR_P.
......@@ -222,6 +223,10 @@ struct template_parm_index_s GTY(())
HOST_WIDE_INT level;
HOST_WIDE_INT orig_level;
tree decl;
/* When true, indicates that this parameter is actually a parameter
pack, for variadic templates. */
BOOL_BITFIELD parameter_pack;
};
typedef struct template_parm_index_s template_parm_index;
......@@ -469,6 +474,13 @@ struct tree_static_assert GTY (())
location_t location;
};
struct tree_argument_pack_select GTY (())
{
struct tree_common common;
tree argument_pack;
int index;
};
enum cp_tree_node_structure_enum {
TS_CP_GENERIC,
TS_CP_IDENTIFIER,
......@@ -481,6 +493,7 @@ enum cp_tree_node_structure_enum {
TS_CP_WRAPPER,
TS_CP_DEFAULT_ARG,
TS_CP_STATIC_ASSERT,
TS_CP_ARGUMENT_PACK_SELECT,
LAST_TS_CP_ENUM
};
......@@ -499,6 +512,8 @@ union lang_tree_node GTY((desc ("cp_tree_node_structure (&%h)"),
struct lang_identifier GTY ((tag ("TS_CP_IDENTIFIER"))) identifier;
struct tree_static_assert GTY ((tag ("TS_CP_STATIC_ASSERT")))
static_assertion;
struct tree_argument_pack_select GTY ((tag ("TS_CP_ARGUMENT_PACK_SELECT")))
argument_pack_select;
};
......@@ -2188,7 +2203,7 @@ extern void decl_shadowed_for_var_insert (tree, tree);
/* Nonzero if the template arguments is actually a vector of vectors,
rather than just a vector. */
#define TMPL_ARGS_HAVE_MULTIPLE_LEVELS(NODE) \
(NODE && TREE_VEC_ELT (NODE, 0) \
(NODE && TREE_VEC_ELT (NODE, 0) \
&& TREE_CODE (TREE_VEC_ELT (NODE, 0)) == TREE_VEC)
/* The depth of a template argument vector. When called directly by
......@@ -2295,6 +2310,84 @@ extern void decl_shadowed_for_var_insert (tree, tree);
the class definition is complete. */
#define TEMPLATE_PARMS_FOR_INLINE(NODE) TREE_LANG_FLAG_1 (NODE)
/* Determine if a parameter (i.e., a PARM_DECL) is a function
parameter pack. */
#define FUNCTION_PARAMETER_PACK_P(NODE) \
(DECL_LANG_FLAG_1 (PARM_DECL_CHECK (NODE)))
/* Determines if NODE is an expansion of one or more parameter packs,
e.g., a TYPE_PACK_EXPANSION or EXPR_PACK_EXPANSION. */
#define PACK_EXPANSION_P(NODE) \
(TREE_CODE (NODE) == TYPE_PACK_EXPANSION \
|| TREE_CODE (NODE) == EXPR_PACK_EXPANSION)
/* Extracts the type or expression pattern from a TYPE_PACK_EXPANSION or
EXPR_PACK_EXPANSION. */
#define PACK_EXPANSION_PATTERN(NODE) \
(TREE_CODE (NODE) == TYPE_PACK_EXPANSION? TREE_TYPE (NODE) \
: TREE_OPERAND (NODE, 0))
/* Sets the type or expression pattern for a TYPE_PACK_EXPANSION or
EXPR_PACK_EXPANSION. */
#define SET_PACK_EXPANSION_PATTERN(NODE,VALUE) \
if (TREE_CODE (NODE) == TYPE_PACK_EXPANSION) \
TREE_TYPE (NODE) = VALUE; \
else \
TREE_OPERAND (NODE, 0) = VALUE
/* The list of parameter packs used in the PACK_EXPANSION_* node. The
TREE_VALUE of each TREE_LIST contains the parameter packs. */
#define PACK_EXPANSION_PARAMETER_PACKS(NODE) TREE_CHAIN (NODE)
/* Determine if this is an argument pack. */
#define ARGUMENT_PACK_P(NODE) \
(TREE_CODE (NODE) == TYPE_ARGUMENT_PACK \
|| TREE_CODE (NODE) == NONTYPE_ARGUMENT_PACK)
/* The arguments stored in an argument pack. Arguments are stored in a
TREE_VEC, which may have length zero. */
#define ARGUMENT_PACK_ARGS(NODE) \
(TREE_CODE (NODE) == TYPE_ARGUMENT_PACK? TREE_TYPE (NODE) \
: TREE_OPERAND (NODE, 0))
/* Set the arguments stored in an argument pack. VALUE must be a
TREE_VEC. */
#define SET_ARGUMENT_PACK_ARGS(NODE,VALUE) \
if (TREE_CODE (NODE) == TYPE_ARGUMENT_PACK) \
TREE_TYPE (NODE) = VALUE; \
else \
TREE_OPERAND (NODE, 0) = VALUE
/* Whether the argument pack is "incomplete", meaning that more
arguments can still be deduced. Incomplete argument packs are only
used when the user has provided an explicit template argument list
for a variadic function template. Some of the explicit template
arguments will be placed into the beginning of the argument pack,
but additional arguments might still be deduced. */
#define ARGUMENT_PACK_INCOMPLETE_P(NODE) \
TREE_LANG_FLAG_0 (ARGUMENT_PACK_ARGS (NODE))
/* When ARGUMENT_PACK_INCOMPLETE_P, stores the explicit template
arguments used to fill this pack. */
#define ARGUMENT_PACK_EXPLICIT_ARGS(NODE) \
TREE_TYPE (ARGUMENT_PACK_ARGS (NODE))
/* In an ARGUMENT_PACK_SELECT, the argument pack from which an
argument will be selected. */
#define ARGUMENT_PACK_SELECT_FROM_PACK(NODE) \
(((struct tree_argument_pack_select *)ARGUMENT_PACK_SELECT_CHECK (NODE))->argument_pack)
/* In an ARGUMENT_PACK_SELECT, the index of the argument we want to
select. */
#define ARGUMENT_PACK_SELECT_INDEX(NODE) \
(((struct tree_argument_pack_select *)ARGUMENT_PACK_SELECT_CHECK (NODE))->index)
/* In an ARGUMENT_PACK_SELECT, the actual underlying argument that the
ARGUMENT_PACK_SELECT represents. */
#define ARGUMENT_PACK_SELECT_ARG(NODE) \
TREE_VEC_ELT (ARGUMENT_PACK_ARGS (ARGUMENT_PACK_SELECT_FROM_PACK (NODE)), \
ARGUMENT_PACK_SELECT_INDEX (NODE));
/* In a FUNCTION_DECL, the saved language-specific per-function data. */
#define DECL_SAVED_FUNCTION_DATA(NODE) \
(DECL_LANG_SPECIFIC (FUNCTION_DECL_CHECK (NODE)) \
......@@ -3612,6 +3705,7 @@ enum overload_flags { NO_SPECIAL = 0, DTOR_FLAG, OP_FLAG, TYPENAME_FLAG };
#define TEMPLATE_PARM_DESCENDANTS(NODE) (TREE_CHAIN (NODE))
#define TEMPLATE_PARM_ORIG_LEVEL(NODE) (TEMPLATE_PARM_INDEX_CAST (NODE)->orig_level)
#define TEMPLATE_PARM_DECL(NODE) (TEMPLATE_PARM_INDEX_CAST (NODE)->decl)
#define TEMPLATE_PARM_PARAMETER_PACK(NODE) (TEMPLATE_PARM_INDEX_CAST (NODE)->parameter_pack)
/* These macros are for accessing the fields of TEMPLATE_TYPE_PARM,
TEMPLATE_TEMPLATE_PARM and BOUND_TEMPLATE_TEMPLATE_PARM nodes. */
......@@ -3626,6 +3720,8 @@ enum overload_flags { NO_SPECIAL = 0, DTOR_FLAG, OP_FLAG, TYPENAME_FLAG };
(TEMPLATE_PARM_ORIG_LEVEL (TEMPLATE_TYPE_PARM_INDEX (NODE)))
#define TEMPLATE_TYPE_DECL(NODE) \
(TEMPLATE_PARM_DECL (TEMPLATE_TYPE_PARM_INDEX (NODE)))
#define TEMPLATE_TYPE_PARAMETER_PACK(NODE) \
(TEMPLATE_PARM_PARAMETER_PACK (TEMPLATE_TYPE_PARM_INDEX (NODE)))
/* These constants can used as bit flags in the process of tree formatting.
......@@ -3812,6 +3908,9 @@ struct cp_declarator {
cdk_id and cdk_error, guaranteed to be NULL. */
cp_declarator *declarator;
location_t id_loc; /* Currently only set for cdk_id. */
/* Whether we parsed an ellipsis (`...') just before the declarator,
to indicate this is a parameter pack. */
bool parameter_pack_p;
union {
/* For identifiers. */
struct {
......@@ -4104,6 +4203,7 @@ extern const char *lang_decl_name (tree, int);
extern const char *language_to_string (enum languages);
extern const char *class_key_or_enum_as_string (tree);
extern void print_instantiation_context (void);
extern void maybe_warn_variadic_templates (void);
/* in except.c */
extern void init_exception_processing (void);
......@@ -4194,7 +4294,7 @@ extern void end_specialization (void);
extern void begin_explicit_instantiation (void);
extern void end_explicit_instantiation (void);
extern tree check_explicit_specialization (tree, tree, int, int);
extern tree process_template_parm (tree, tree, bool);
extern tree process_template_parm (tree, tree, bool, bool);
extern tree end_template_parm_list (tree);
extern void end_template_decl (void);
extern tree push_template_decl (tree);
......@@ -4215,6 +4315,11 @@ extern void do_decl_instantiation (tree, tree);
extern void do_type_instantiation (tree, tree, tsubst_flags_t);
extern tree instantiate_decl (tree, int, bool);
extern int comp_template_parms (tree, tree);
extern bool uses_parameter_packs (tree);
extern bool template_parameter_pack_p (tree);
extern bool template_parms_variadic_p (tree);
extern tree make_pack_expansion (tree);
extern void check_for_bare_parameter_packs (tree);
extern int template_class_depth (tree);
extern int is_specialization_of (tree, tree);
extern bool is_specialization_of_friend (tree, tree);
......
......@@ -659,6 +659,7 @@ pp_cxx_delete_expression (cxx_pretty_printer *pp, tree t)
unary-operator cast-expression
sizeof unary-expression
sizeof ( type-id )
sizeof ... ( identifier )
new-expression
delete-expression
......@@ -686,6 +687,21 @@ pp_cxx_unary_expression (cxx_pretty_printer *pp, tree t)
break;
case SIZEOF_EXPR:
if (PACK_EXPANSION_P (TREE_OPERAND (t, 0)))
{
pp_cxx_identifier (pp, "sizeof");
pp_cxx_identifier (pp, "...");
pp_cxx_whitespace (pp);
pp_cxx_left_paren (pp);
if (TYPE_P (TREE_OPERAND (t, 0)))
pp_cxx_type_id (pp, TREE_OPERAND (t, 0));
else
pp_unary_expression (pp, TREE_OPERAND (t, 0));
pp_cxx_right_paren (pp);
break;
}
/* Fall through */
case ALIGNOF_EXPR:
pp_cxx_identifier (pp, code == SIZEOF_EXPR ? "sizeof" : "__alignof__");
pp_cxx_whitespace (pp);
......@@ -1000,6 +1016,24 @@ pp_cxx_expression (cxx_pretty_printer *pp, tree t)
pp_cxx_expression (pp, t);
break;
case EXPR_PACK_EXPANSION:
pp_cxx_expression (pp, PACK_EXPANSION_PATTERN (t));
pp_cxx_identifier (pp, "...");
break;
case NONTYPE_ARGUMENT_PACK:
{
tree args = ARGUMENT_PACK_ARGS (t);
int i, len = TREE_VEC_LENGTH (args);
for (i = 0; i < len; ++i)
{
if (i > 0)
pp_cxx_separate_with (pp, ',');
pp_cxx_expression (pp, TREE_VEC_ELT (args, i));
}
}
break;
default:
pp_c_expression (pp_c_base (pp), t);
break;
......@@ -1290,6 +1324,7 @@ static void
pp_cxx_exception_specification (cxx_pretty_printer *pp, tree t)
{
tree ex_spec = TYPE_RAISES_EXCEPTIONS (t);
bool need_comma = false;
if (!TYPE_NOTHROW_P (t) && ex_spec == NULL)
return;
......@@ -1297,9 +1332,28 @@ pp_cxx_exception_specification (cxx_pretty_printer *pp, tree t)
pp_cxx_left_paren (pp);
for (; ex_spec && TREE_VALUE (ex_spec); ex_spec = TREE_CHAIN (ex_spec))
{
pp_cxx_type_id (pp, TREE_VALUE (ex_spec));
if (TREE_CHAIN (ex_spec))
pp_cxx_separate_with (pp, ',');
tree type = TREE_VALUE (ex_spec);
tree argpack = NULL_TREE;
int i, len = 1;
if (ARGUMENT_PACK_P (type))
{
argpack = ARGUMENT_PACK_ARGS (type);
len = TREE_VEC_LENGTH (argpack);
}
for (i = 0; i < len; ++i)
{
if (argpack)
type = TREE_VEC_ELT (argpack, i);
if (need_comma)
pp_cxx_separate_with (pp, ',');
else
need_comma = true;
pp_cxx_type_id (pp, type);
}
}
pp_cxx_right_paren (pp);
}
......@@ -1323,6 +1377,13 @@ pp_cxx_direct_declarator (cxx_pretty_printer *pp, tree t)
if (DECL_NAME (t))
{
pp_cxx_space_for_pointer_operator (pp, TREE_TYPE (t));
if ((TREE_CODE (t) == PARM_DECL && FUNCTION_PARAMETER_PACK_P (t))
|| template_parameter_pack_p (t))
/* A function parameter pack or non-type template
parameter pack. */
pp_cxx_identifier (pp, "...");
pp_cxx_id_expression (pp, DECL_NAME (t));
}
pp_cxx_abstract_declarator (pp, TREE_TYPE (t));
......@@ -1388,8 +1449,16 @@ pp_cxx_ctor_initializer (cxx_pretty_printer *pp, tree t)
pp_cxx_whitespace (pp);
for (; t; t = TREE_CHAIN (t))
{
pp_cxx_primary_expression (pp, TREE_PURPOSE (t));
tree purpose = TREE_PURPOSE (t);
bool is_pack = PACK_EXPANSION_P (purpose);
if (is_pack)
pp_cxx_primary_expression (pp, PACK_EXPANSION_PATTERN (purpose));
else
pp_cxx_primary_expression (pp, purpose);
pp_cxx_call_argument_list (pp, TREE_VALUE (t));
if (is_pack)
pp_cxx_identifier (pp, "...");
if (TREE_CHAIN (t))
pp_cxx_separate_with (pp, ',');
}
......@@ -1510,6 +1579,11 @@ pp_cxx_type_id (cxx_pretty_printer *pp, tree t)
pp_cxx_type_specifier_seq (pp, t);
break;
case TYPE_PACK_EXPANSION:
pp_cxx_type_id (pp, PACK_EXPANSION_PATTERN (t));
pp_cxx_identifier (pp, "...");
break;
default:
pp_c_type_id (pp_c_base (pp), t);
break;
......@@ -1519,30 +1593,50 @@ pp_cxx_type_id (cxx_pretty_printer *pp, tree t)
}
/* template-argument-list:
template-argument
template-argument-list, template-argument
template-argument ...(opt)
template-argument-list, template-argument ...(opt)
template-argument:
assignment-expression
type-id
template-name */
template-name */
static void
pp_cxx_template_argument_list (cxx_pretty_printer *pp, tree t)
{
int i;
bool need_comma = false;
if (t == NULL)
return;
for (i = 0; i < TREE_VEC_LENGTH (t); ++i)
{
tree arg = TREE_VEC_ELT (t, i);
if (i != 0)
pp_cxx_separate_with (pp, ',');
if (TYPE_P (arg) || (TREE_CODE (arg) == TEMPLATE_DECL
&& TYPE_P (DECL_TEMPLATE_RESULT (arg))))
pp_cxx_type_id (pp, arg);
else
pp_cxx_expression (pp, arg);
tree argpack = NULL_TREE;
int idx, len = 1;
if (ARGUMENT_PACK_P (arg))
{
argpack = ARGUMENT_PACK_ARGS (arg);
len = TREE_VEC_LENGTH (argpack);
}
for (idx = 0; idx < len; idx++)
{
if (argpack)
arg = TREE_VEC_ELT (argpack, idx);
if (need_comma)
pp_cxx_separate_with (pp, ',');
else
need_comma = true;
if (TYPE_P (arg) || (TREE_CODE (arg) == TEMPLATE_DECL
&& TYPE_P (DECL_TEMPLATE_RESULT (arg))))
pp_cxx_type_id (pp, arg);
else
pp_cxx_expression (pp, arg);
}
}
}
......@@ -1837,11 +1931,11 @@ pp_cxx_template_parameter_list (cxx_pretty_printer *pp, tree t)
parameter-declaration
type-parameter:
class identifier(opt)
class identifier(op) = type-id
class ...(opt) identifier(opt)
class identifier(opt) = type-id
typename identifier(opt)
typename identifier(opt) = type-id
template < template-parameter-list > class identifier(opt)
typename ...(opt) identifier(opt) = type-id
template < template-parameter-list > class ...(opt) identifier(opt)
template < template-parameter-list > class identifier(opt) = template-name */
static void
......@@ -1852,6 +1946,8 @@ pp_cxx_template_parameter (cxx_pretty_printer *pp, tree t)
{
case TYPE_DECL:
pp_cxx_identifier (pp, "class");
if (TEMPLATE_TYPE_PARAMETER_PACK (TREE_TYPE (t)))
pp_cxx_identifier (pp, "...");
if (DECL_NAME (parameter))
pp_cxx_tree_identifier (pp, DECL_NAME (parameter));
/* FIXME: Chech if we should print also default argument. */
......
......@@ -6967,6 +6967,7 @@ grokdeclarator (const cp_declarator *declarator,
cp_storage_class storage_class;
bool unsigned_p, signed_p, short_p, long_p, thread_p;
bool type_was_error_mark_node = false;
bool parameter_pack_p = declarator? declarator->parameter_pack_p : false;
bool set_no_warning = false;
signed_p = declspecs->specs[(int)ds_signed];
......@@ -7938,6 +7939,16 @@ grokdeclarator (const cp_declarator *declarator,
attrlist = &returned_attrs;
}
/* Handle parameter packs. */
if (parameter_pack_p)
{
if (decl_context == PARM)
/* Turn the type into a pack expansion.*/
type = make_pack_expansion (type);
else
error ("non-parameter %qs cannot be a parameter pack", name);
}
/* Did array size calculations overflow? */
if (TREE_CODE (type) == ARRAY_TYPE
......@@ -8940,6 +8951,11 @@ grokparms (cp_parameter_declarator *first_parm, tree *parms)
init = check_default_argument (decl, init);
}
if (TREE_CODE (decl) == PARM_DECL
&& FUNCTION_PARAMETER_PACK_P (decl)
&& parm->next)
error ("parameter packs must be at the end of the parameter list");
TREE_CHAIN (decl) = decls;
decls = decl;
result = tree_cons (init, type, result);
......@@ -9941,6 +9957,8 @@ xref_basetypes (tree ref, tree base_list)
if (access == access_default_node)
access = default_access;
if (PACK_EXPANSION_P (basetype))
basetype = PACK_EXPANSION_PATTERN (basetype);
if (TREE_CODE (basetype) == TYPE_DECL)
basetype = TREE_TYPE (basetype);
if (TREE_CODE (basetype) != RECORD_TYPE
......@@ -9986,6 +10004,11 @@ xref_basetypes (tree ref, tree base_list)
error ("duplicate base type %qT invalid", basetype);
return false;
}
if (PACK_EXPANSION_P (TREE_VALUE (base_list)))
/* Regenerate the pack expansion for the bases. */
basetype = make_pack_expansion (basetype);
TYPE_MARKED_P (basetype) = 1;
base_binfo = copy_binfo (base_binfo, basetype, ref,
......@@ -11695,6 +11718,7 @@ cp_tree_node_structure (union lang_tree_node * t)
case PTRMEM_CST: return TS_CP_PTRMEM;
case BASELINK: return TS_CP_BASELINK;
case STATIC_ASSERT: return TS_CP_STATIC_ASSERT;
case ARGUMENT_PACK_SELECT: return TS_CP_ARGUMENT_PACK_SELECT;
default: return TS_CP_GENERIC;
}
}
......
......@@ -136,6 +136,12 @@ cp_build_parm_decl (tree name, tree type)
sees templates. */
if (!processing_template_decl)
DECL_ARG_TYPE (parm) = type_passed_as (type);
/* If the type is a pack expansion, then we have a function
parameter pack. */
if (type && TREE_CODE (type) == TYPE_PACK_EXPANSION)
FUNCTION_PARAMETER_PACK_P (parm) = 1;
return parm;
}
......
......@@ -138,7 +138,9 @@ dump_scope (tree scope, int flags)
static void
dump_template_argument (tree arg, int flags)
{
if (TYPE_P (arg) || TREE_CODE (arg) == TEMPLATE_DECL)
if (ARGUMENT_PACK_P (arg))
dump_template_argument_list (ARGUMENT_PACK_ARGS (arg), flags);
else if (TYPE_P (arg) || TREE_CODE (arg) == TEMPLATE_DECL)
dump_type (arg, flags & ~TFF_CLASS_KEY_OR_ENUM);
else
dump_expr (arg, (flags | TFF_EXPR_IN_PARENS) & ~TFF_CLASS_KEY_OR_ENUM);
......@@ -156,9 +158,17 @@ dump_template_argument_list (tree args, int flags)
for (i = 0; i< n; ++i)
{
if (need_comma)
tree arg = TREE_VEC_ELT (args, i);
/* Only print a comma if we know there is an argument coming. In
the case of an empty template argument pack, no actual
argument will be printed. */
if (need_comma
&& (!ARGUMENT_PACK_P (arg)
|| TREE_VEC_LENGTH (ARGUMENT_PACK_ARGS (arg)) > 0))
pp_separate_with_comma (cxx_pp);
dump_template_argument (TREE_VEC_ELT (args, i), flags);
dump_template_argument (arg, flags);
need_comma = 1;
}
}
......@@ -182,6 +192,8 @@ dump_template_parameter (tree parm, int flags)
if (flags & TFF_DECL_SPECIFIERS)
{
pp_cxx_identifier (cxx_pp, "class");
if (TEMPLATE_TYPE_PARAMETER_PACK (TREE_TYPE (p)))
pp_cxx_identifier (cxx_pp, "...");
if (DECL_NAME (p))
pp_cxx_tree_identifier (cxx_pp, DECL_NAME (p));
}
......@@ -378,6 +390,11 @@ dump_type (tree t, int flags)
pp_cxx_right_paren (cxx_pp);
break;
case TYPE_PACK_EXPANSION:
dump_type (PACK_EXPANSION_PATTERN (t), flags);
pp_cxx_identifier (cxx_pp, "...");
break;
default:
pp_unsupported_tree (cxx_pp, t);
/* Fall through to error. */
......@@ -1102,8 +1119,7 @@ dump_function_decl (tree t, int flags)
static void
dump_parameters (tree parmtypes, int flags)
{
int first;
int first = 1;
pp_cxx_left_paren (cxx_pp);
for (first = 1; parmtypes != void_list_node;
......@@ -1117,7 +1133,22 @@ dump_parameters (tree parmtypes, int flags)
pp_cxx_identifier (cxx_pp, "...");
break;
}
dump_type (TREE_VALUE (parmtypes), flags);
if (ARGUMENT_PACK_P (TREE_VALUE (parmtypes)))
{
tree types = ARGUMENT_PACK_ARGS (TREE_VALUE (parmtypes));
int i, len = TREE_VEC_LENGTH (types);
first = 1;
for (i = 0; i < len; ++i)
{
if (!first)
pp_separate_with_comma (cxx_pp);
first = 0;
dump_type (TREE_VEC_ELT (types, i), flags);
}
}
else
dump_type (TREE_VALUE (parmtypes), flags);
if ((flags & TFF_FUNCTION_DEFAULT_ARGUMENTS) && TREE_PURPOSE (parmtypes))
{
......@@ -1240,14 +1271,19 @@ dump_template_parms (tree info, int primary, int flags)
{
tree arg = TREE_VEC_ELT (args, ix);
if (ix)
pp_separate_with_comma (cxx_pp);
if (!arg)
pp_identifier (cxx_pp, "<template parameter error>");
else
dump_template_argument (arg, flags);
}
/* Only print a comma if we know there is an argument coming. In
the case of an empty template argument pack, no actual
argument will be printed. */
if (ix
&& (!ARGUMENT_PACK_P (arg)
|| TREE_VEC_LENGTH (ARGUMENT_PACK_ARGS (arg)) > 0))
pp_separate_with_comma (cxx_pp);
if (!arg)
pp_identifier (cxx_pp, "<template parameter error>");
else
dump_template_argument (arg, flags);
}
}
else if (primary)
{
......@@ -1946,6 +1982,11 @@ dump_expr (tree t, int flags)
dump_expr (TREE_OPERAND (t, 0), flags);
break;
case EXPR_PACK_EXPANSION:
dump_expr (PACK_EXPANSION_PATTERN (t), flags);
pp_cxx_identifier (cxx_pp, "...");
break;
/* This list is incomplete, but should suffice for now.
It is very important that `sorry' does not call
`report_error_function'. That could cause an infinite loop. */
......@@ -2464,3 +2505,14 @@ cp_cpp_error (cpp_reader *pfile ATTRIBUTE_UNUSED, int level,
input_location, dlevel);
report_diagnostic (&diagnostic);
}
/* Warn about the use of variadic templates when appropriate. */
void
maybe_warn_variadic_templates (void)
{
if ((!flag_cpp0x || flag_iso) && !in_system_header)
/* We really want to surpress this warning in system headers,
because libstdc++ uses variadic templates even when we aren't
in C++0x mode. */
pedwarn ("ISO C++ does not include variadic templates");
}
......@@ -1645,6 +1645,11 @@ write_type (tree type)
write_type (TREE_TYPE (type));
break;
case TYPE_PACK_EXPANSION:
write_string ("U10__variadic");
write_type (PACK_EXPANSION_PATTERN (type));
break;
default:
gcc_unreachable ();
}
......@@ -2302,7 +2307,15 @@ write_template_arg (tree node)
G.need_abi_warning = 1;
}
if (TYPE_P (node))
if (ARGUMENT_PACK_P (node))
{
/* Expand the template argument pack. */
tree args = ARGUMENT_PACK_ARGS (node);
int i, length = TREE_VEC_LENGTH (args);
for (i = 0; i < length; ++i)
write_template_arg (TREE_VEC_ELT (args, i));
}
else if (TYPE_P (node))
write_type (node);
else if (code == TEMPLATE_DECL)
/* A template appearing as a template arg is a template template arg. */
......
......@@ -4563,6 +4563,18 @@ arg_assoc_type (struct arg_lookup *k, tree type)
case LANG_TYPE:
gcc_assert (type == unknown_type_node);
return false;
case TYPE_PACK_EXPANSION:
return arg_assoc_type (k, PACK_EXPANSION_PATTERN (type));
case TYPE_ARGUMENT_PACK:
{
tree args = ARGUMENT_PACK_ARGS (type);
int i, len = TREE_VEC_LENGTH (args);
for (i = 0; i < len; i++)
if (arg_assoc_type (k, TREE_VEC_ELT (args, i)))
return true;
}
break;
default:
gcc_unreachable ();
}
......
......@@ -150,3 +150,6 @@ DEF_SIMPLE_OPERATOR ("?:", COND_EXPR, "qu", 3)
/* Miscellaneous. */
DEF_SIMPLE_OPERATOR ("()", CALL_EXPR, "cl", -1)
/* Variadic templates extension. */
DEF_SIMPLE_OPERATOR ("...", EXPR_PACK_EXPANSION, "pu", 1)
......@@ -509,6 +509,8 @@ finish_cond (tree *cond_p, tree expr)
tree cond = pop_stmt_list (*cond_p);
if (TREE_CODE (cond) == DECL_EXPR)
expr = cond;
check_for_bare_parameter_packs (expr);
}
*cond_p = expr;
}
......@@ -618,6 +620,8 @@ finish_expr_stmt (tree expr)
else if (!type_dependent_expression_p (expr))
convert_to_void (build_non_dependent_expr (expr), "statement");
check_for_bare_parameter_packs (expr);
/* Simplification of inner statement expressions, compound exprs,
etc can result in us already having an EXPR_STMT. */
if (TREE_CODE (expr) != CLEANUP_POINT_EXPR)
......@@ -866,6 +870,7 @@ finish_for_expr (tree expr, tree for_stmt)
else if (!type_dependent_expression_p (expr))
convert_to_void (build_non_dependent_expr (expr), "3rd expression in for");
expr = maybe_cleanup_point_expr_void (expr);
check_for_bare_parameter_packs (expr);
FOR_EXPR (for_stmt) = expr;
}
......@@ -966,6 +971,7 @@ finish_switch_cond (tree cond, tree switch_stmt)
add_stmt (switch_stmt);
push_switch (switch_stmt);
SWITCH_STMT_BODY (switch_stmt) = push_stmt_list ();
check_for_bare_parameter_packs (cond);
}
/* Finish the body of a switch-statement, which may be given by
......@@ -1362,7 +1368,22 @@ finish_mem_initializers (tree mem_inits)
mem_inits = nreverse (mem_inits);
if (processing_template_decl)
add_stmt (build_min_nt (CTOR_INITIALIZER, mem_inits));
{
tree mem;
for (mem = mem_inits; mem; mem = TREE_CHAIN (mem))
{
/* If the TREE_PURPOSE is a TYPE_PACK_EXPANSION, skip the
check for bare parameter packs in the TREE_VALUE, because
any parameter packs in the TREE_VALUE have already been
bound as part of the TREE_PURPOSE. See
make_pack_expansion for more information. */
if (TREE_CODE (TREE_PURPOSE (mem)) != TYPE_PACK_EXPANSION)
check_for_bare_parameter_packs (TREE_VALUE (mem));
}
add_stmt (build_min_nt (CTOR_INITIALIZER, mem_inits));
}
else
emit_mem_initializers (mem_inits);
}
......
......@@ -2233,12 +2233,16 @@ cp_walk_subtrees (tree *tp, int *walk_subtrees_p, walk_tree_fn func,
case TEMPLATE_TYPE_PARM:
case TYPENAME_TYPE:
case TYPEOF_TYPE:
case BASELINK:
/* None of these have subtrees other than those already walked
above. */
*walk_subtrees_p = 0;
break;
case BASELINK:
WALK_SUBTREE (BASELINK_FUNCTIONS (*tp));
*walk_subtrees_p = 0;
break;
case TINST_LEVEL:
WALK_SUBTREE (TINST_DECL (*tp));
*walk_subtrees_p = 0;
......@@ -2264,6 +2268,38 @@ cp_walk_subtrees (tree *tp, int *walk_subtrees_p, walk_tree_fn func,
WALK_SUBTREE (TYPE_PTRMEMFUNC_FN_TYPE (*tp));
break;
case TYPE_ARGUMENT_PACK:
case NONTYPE_ARGUMENT_PACK:
{
tree args = ARGUMENT_PACK_ARGS (*tp);
int i, len = TREE_VEC_LENGTH (args);
for (i = 0; i < len; i++)
WALK_SUBTREE (TREE_VEC_ELT (args, i));
}
break;
case TYPE_PACK_EXPANSION:
WALK_SUBTREE (TREE_TYPE (*tp));
*walk_subtrees_p = 0;
break;
case EXPR_PACK_EXPANSION:
WALK_SUBTREE (TREE_OPERAND (*tp, 0));
*walk_subtrees_p = 0;
break;
case CAST_EXPR:
if (TREE_TYPE (*tp))
WALK_SUBTREE (TREE_TYPE (*tp));
{
int i;
for (i = 0; i < TREE_CODE_LENGTH (TREE_CODE (*tp)); ++i)
WALK_SUBTREE (TREE_OPERAND (*tp, i));
}
*walk_subtrees_p = 0;
break;
default:
input_location = save_locus;
return NULL_TREE;
......
......@@ -991,7 +991,9 @@ structural_comptypes (tree t1, tree t2, int strict)
case TEMPLATE_TEMPLATE_PARM:
case BOUND_TEMPLATE_TEMPLATE_PARM:
if (TEMPLATE_TYPE_IDX (t1) != TEMPLATE_TYPE_IDX (t2)
|| TEMPLATE_TYPE_LEVEL (t1) != TEMPLATE_TYPE_LEVEL (t2))
|| TEMPLATE_TYPE_LEVEL (t1) != TEMPLATE_TYPE_LEVEL (t2)
|| (TEMPLATE_TYPE_PARAMETER_PACK (t1)
!= TEMPLATE_TYPE_PARAMETER_PACK (t2)))
return false;
if (!comp_template_parms
(DECL_TEMPLATE_PARMS (TEMPLATE_TEMPLATE_PARM_TEMPLATE_DECL (t1)),
......@@ -1050,7 +1052,9 @@ structural_comptypes (tree t1, tree t2, int strict)
case TEMPLATE_TYPE_PARM:
if (TEMPLATE_TYPE_IDX (t1) != TEMPLATE_TYPE_IDX (t2)
|| TEMPLATE_TYPE_LEVEL (t1) != TEMPLATE_TYPE_LEVEL (t2))
|| TEMPLATE_TYPE_LEVEL (t1) != TEMPLATE_TYPE_LEVEL (t2)
|| (TEMPLATE_TYPE_PARAMETER_PACK (t1)
!= TEMPLATE_TYPE_PARAMETER_PACK (t2)))
return false;
break;
......@@ -1080,6 +1084,10 @@ structural_comptypes (tree t1, tree t2, int strict)
return false;
break;
case TYPE_PACK_EXPANSION:
return same_type_p (PACK_EXPANSION_PATTERN (t1),
PACK_EXPANSION_PATTERN (t2));
default:
return false;
}
......@@ -6543,6 +6551,7 @@ check_return_expr (tree retval, bool *no_warning)
if (processing_template_decl)
{
current_function_returns_value = 1;
check_for_bare_parameter_packs (retval);
return retval;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment