5.12.5.2.2.2.2. Default arguments
If arg exhausted, but parm doesn’t, parm must contain default arguments, for which the list should be terminated by the special void_list_node . Then in corresponding node, TREE_VALUE holds the type, and TREE_PURPOSE is the expression of the default argument.
4258 tree
4259 convert_default_arg (tree type, tree arg, tree fn, int parmnum) in call.c
4260 {
4261 /* If the ARG is an unparsed default argument expression, the
4262 conversion cannot be performed. */
4263 if (TREE_CODE (arg) == DEFAULT_ARG)
4264 {
4265 error ("the default argument for parameter %d of `%D' has "
4266 "not yet been parsed",
4267 parmnum, fn);
4268 return error_mark_node;
4269 }
4270
4271 if (fn && DECL_TEMPLATE_INFO (fn))
4272 arg = tsubst_default_argument (fn, type, arg);
4273
4274 arg = break_out_target_exprs (arg);
4275
4276 if (TREE_CODE (arg) == CONSTRUCTOR)
4277 {
4278 arg = digest_init (type, arg, 0);
4279 arg = convert_for_initialization (0, type, arg, LOOKUP_NORMAL,
4280 "default argument", fn, parmnum);
4281 }
4282 else
4283 {
4284 /* This could get clobbered by the following call. */
4285 if (TREE_HAS_CONSTRUCTOR (arg))
4286 arg = copy_node (arg);
4287
4288 arg = convert_for_initialization (0, type, arg, LOOKUP_NORMAL,
4289 "default argument", fn, parmnum);
4290 arg = convert_for_arg_passing (type, arg);
4291 }
4292
4293 return arg;
4294 }
Node of DEFAULT_ARG is created for unparsed default argument. Remember during parsing class definition, any default argument is cached by DEFAULT_ARG, which will be parsed after the parsing. So DEFAULT_ARG should not be present here (if so, it may be missing “};” in the class definition), and at this point, the front-end doesn’t know how to handle node of this kind.
Then arg passed for below function at line 4274, is the default argument shared by all invocations of the function. However, as [3] defines, “ Default arguments are evaluated each time the function is called ”, so before really evaluate the expression of the default argument, it needs prepare appropriate copy of arg and update this local temparories acclaimed as below.
1259 tree
1260 break_out_target_exprs (tree t) in cp/tree.c
1261 {
1262 static int target_remap_count;
1263 static splay_tree target_remap;
1264
1265 if (!target_remap_count++)
1266 target_remap = splay_tree_new (splay_tree_compare_pointers,
1267 /*splay_tree_delete_key_fn=*/ NULL,
1268 /*splay_tree_delete_value_fn=*/ NULL);
1269 walk_tree (&t, bot_manip , target_remap, NULL);
1270 walk_tree (&t, bot_replace , target_remap, NULL);
1271
1272 if (!--target_remap_count)
1273 {
1274 splay_tree_delete (target_remap);
1275 target_remap = NULL;
1276 }
1277
1278 return t;
1279 }
Above walk_tree traverses the subtree rooted by t and executes provided function upon nodes selected by the code of t . In first traversal, below function is used. Note that the function always returns NULL (copy_tree_r below returns NULL) to force walk_tree to do full traversal with depth first (i.e. tp maybe a tree_list, in which node can contain operands which in turn can be tree_list again and so on, the visit should be from bottom up); however whether stepping into the sub-tree (i.e. operand) is controlled by local variable walk_subtrees (which is passes as argument walk_subtrees in below function). Before processing the tree node with the specified function, walk_subtrees is set as 1; it is the specified function to decide whether the node is of interesting and needs to be stepped into if no result gotten.
1182 static tree
1183 bot_manip (tree* tp, int* walk_subtrees, void* data) in cp/tree.c
1184 {
1185 splay_tree target_remap = ((splay_tree) data);
1186 tree t = *tp;
1187
1188 if (TREE_CONSTANT (t))
1189 {
1190 /* There can't be any TARGET_EXPRs or their slot variables below
1191 this point. We used to check !TREE_SIDE_EFFECTS, but then we
1192 failed to copy an ADDR_EXPR of the slot VAR_DECL. */
1193 *walk_subtrees = 0;
1194 return NULL_TREE;
1195 }
1196 if (TREE_CODE (t) == TARGET_EXPR)
1197 {
1198 tree u;
1199
1200 if (TREE_CODE (TREE_OPERAND (t, 1)) == AGGR_INIT_EXPR)
1201 {
1202 mark_used (TREE_OPERAND (TREE_OPERAND (TREE_OPERAND (t, 1), 0), 0));
1203 u = build_cplus_new
1204 (TREE_TYPE (t), break_out_target_exprs (TREE_OPERAND (t, 1)));
1205 }
1206 else
1207 {
1208 u = build_target_expr_with_type
1209 (break_out_target_exprs (TREE_OPERAND (t, 1)), TREE_TYPE (t));
1210 }
1211
1212 /* Map the old variable to the new one. */
1213 splay_tree_insert (target_remap,
1214 (splay_tree_key) TREE_OPERAND (t, 0),
1215 (splay_tree_value) TREE_OPERAND (u, 0));
1216
1217 /* Replace the old expression with the new version. */
1218 *tp = u;
1219 /* We don't have to go below this point; the recursive call to
1220 break_out_target_exprs will have handled anything below this
1221 point. */
1222 *walk_subtrees = 0;
1223 return NULL_TREE;
1224 }
1225 else if (TREE_CODE (t) == CALL_EXPR)
1226 mark_used (TREE_OPERAND (TREE_OPERAND (t, 0), 0));
1227
1228 /* Make a copy of this node. */
1229 return copy_tree_r (tp, walk_subtrees, NULL);
1230 }
For node other than constant or TRAGET_EXPR, copy_tree_r copies the node if it is either *_CST (see it is filtered out by TREE_CONSTANT above) or expression or TREE_LIST or TREE_VEC or OVERLOAD (told by C++ hook of tree_chain_matters_p below, and note that walk_subtrees is unchanged here, walk_tree will step into the sub-nodes of the node and continues copying the structure accordingly).
1966 tree
1967 copy_tree_r (tree *tp, int *walk_subtrees, void *data ATTRIBUTE_UNUSED) in tree-inline.c
1968 {
1969 enum tree_code code = TREE_CODE (*tp);
1970
1971 /* We make copies of most nodes. */
1972 if (IS_EXPR_CODE_CLASS (TREE_CODE_CLASS (code))
1973 || TREE_CODE_CLASS (code) == 'c'
1974 || code == TREE_LIST
1975 || code == TREE_VEC
1976 || (*lang_hooks .tree_inlining.tree_chain_matters_p) (*tp))
1977 {
1978 /* Because the chain gets clobbered when we make a copy, we save it
1979 here. */
1980 tree chain = TREE_CHAIN (*tp);
1981
1982 /* Copy the node. */
1983 *tp = copy_node (*tp);
1984
1985 /* Now, restore the chain, if appropriate. That will cause
1986 walk_tree to walk into the chain as well. */
1987 if (code == PARM_DECL || code == TREE_LIST
1988 #ifndef INLINER_FOR_JAVA
1989 || (*lang_hooks .tree_inlining.tree_chain_matters_p) (*tp)
1990 || STATEMENT_CODE_P (code))
1991 TREE_CHAIN (*tp) = chain;
1992
1993 /* For now, we don't update BLOCKs when we make copies. So, we
1994 have to nullify all scope-statements. */
1995 if (TREE_CODE (*tp) == SCOPE_STMT)
1996 SCOPE_STMT_BLOCK (*tp) = NULL_TREE;
1997 #else /* INLINER_FOR_JAVA */
1998 || (*lang_hooks .tree_inlining.tree_chain_matters_p) (*tp))
1999 TREE_CHAIN (*tp) = chain;
2000 #endif /* INLINER_FOR_JAVA */
2001 }
2002 else if (TREE_CODE_CLASS (code) == 't')
2003 *walk_subtrees = 0;
2004
2005 return NULL_TREE;
2006 }
A TARGET_EXPR represents a temporary object. The first operand is a VAR_DECL for the temporary variable. The second operand is the initializer for the temporary. The initializer is evaluated, and copied (bitwise) into the temporary.
An AGGR_INIT_EXPR represents the initialization as the return value of a function call, or as the result of a constructor. An AGGR_INIT_EXPR will only appear as the second operand of a TARGET_EXPR. The first operand to the AGGR_INIT_EXPR is the address of a function to call, just as in a CALL_EXPR. The second operand are the arguments to pass that function, as a TREE_LIST, again in a manner similar to that of a CALL_EXPR. The value of the expression is that returned by the function.
Then if AGGR_INIT_EXPR is used in TRAGET_EXPR, it recurses break_out_target_exprs to copy this node. With the copied expression, build_cplus_new generates code for the initialization.
2007 tree
2008 build_cplus_new (tree type, tree init) in cp/tree.c
2009 {
2010 tree fn;
2011 tree slot;
2012 tree rval;
2013 int is_ctor;
2014
2015 /* Make sure that we're not trying to create an instance of an
2016 abstract class. */
2017 abstract_virtuals_error (NULL_TREE, type);
2018
2019 if (TREE_CODE (init) != CALL_EXPR && TREE_CODE (init) != AGGR_INIT_EXPR)
2020 return convert (type, init);
2021
2022 fn = TREE_OPERAND (init, 0);
2023 is_ctor = (TREE_CODE (fn) == ADDR_EXPR
2024 && TREE_CODE (TREE_OPERAND (fn, 0)) == FUNCTION_DECL
2025 && DECL_CONSTRUCTOR_P (TREE_OPERAND (fn, 0)));
2026
2027 slot = build_local_temp (type);
2028
2029 /* We split the CALL_EXPR into its function and its arguments here.
2030 Then, in expand_expr, we put them back together. The reason for
2031 this is that this expression might be a default argument
2032 expression. In that case, we need a new temporary every time the
2033 expression is used. That's what break_out_target_exprs does; it
2034 replaces every AGGR_INIT_EXPR with a copy that uses a fresh
2035 temporary slot. Then, expand_expr builds up a call-expression
2036 using the new slot. */
2037
2038 /* If we don't need to use a constructor to create an object of this
2039 type, don't mess with AGGR_INIT_EXPR. */
2040 if (is_ctor || TREE_ADDRESSABLE (type))
2041 {
2042 rval = build (AGGR_INIT_EXPR, type, fn, TREE_OPERAND (init, 1), slot);
2043 TREE_SIDE_EFFECTS (rval) = 1;
2044 AGGR_INIT_VIA_CTOR_P (rval) = is_ctor;
2045 }
2046 else
2047 rval = init;
2048
2049 rval = build_target_expr (slot, rval);
2050
2051 return rval;
2052 }
Now it needs update the temporary in the TARGET_EXPR, as we are not within the context generating the original TARGET_EXPR. See it makes the temporary as local by forcing its DECL_CONTEXT by current_function_decl .
253 static tree
254 build_local_temp (tree type) in cp/tree.c
255 {
256 tree slot = build_decl (VAR_DECL, NULL_TREE, type);
257 DECL_ARTIFICIAL (slot) = 1;
258 DECL_CONTEXT (slot) = current_function_decl ;
259 layout_decl (slot, 0);
260 return slot;
261 }
Note if AGGR_INIT_VIA_CTOR_P holds for the AGGR_INIT_EXPR, then the initialization is via a constructor call. AGGR_INIT_EXPR constructed at line 2042 will have this temporary as its third operand, which is always a VAR_DECL. And init is the AGGR_INIT_EXPR in the original TARGET_EXPR (the address of the third operand of the AGGR_INIT_EXPR is taken, and this value replaces the first argument in the argument list. In this case, the value of the expression is the VAR_DECL given by the third operand to the AGGR_INIT_EXPR; constructors do not return a value).
Finally, this new generated TARGET_EXPR is retuned by build_cplus_new .
While for second operand in TARGET_EXPR other than AGGR_INIT_EXPR, the operand is handled by build_target_expr_with_type instead. Here if init is TARGET_EXPR, it must be one that built by break_out_target_exprs at line 1209 in bot_manip , which is the node we expect.
320 tree
321 build_target_expr_with_type (tree init, tree type) in cp/tree.c
322 {
323 tree slot;
324
325 if (TREE_CODE (init) == TARGET_EXPR)
326 return init;
327 else if (CLASS_TYPE_P (type) && !TYPE_HAS_TRIVIAL_INIT_REF (type)
328 && TREE_CODE (init) != COND_EXPR
329 && TREE_CODE (init) != CONSTRUCTOR
330 && TREE_CODE (init) != VA_ARG_EXPR)
331 /* We need to build up a copy constructor call. COND_EXPR is a special
332 case because we already have copies on the arms and we don't want
333 another one here. A CONSTRUCTOR is aggregate initialization, which
334 is handled separately. A VA_ARG_EXPR is magic creation of an
335 aggregate; there's no additional work to be done. */
336 return force_rvalue (init);
337
338 slot = build_local_temp (type);
339 return build_target_expr (slot, init);
340 }
Above if TYPE_HAS_TRIVIAL_INIT_REF is nonzero, it means that copy initialization of the type can use a bitwise copy. For which case, it can simply build TARGET_EXPR; otherwise, it needs perform an lvalue-to-rvalue conversion, including invoking the copy ctor as below.
590 tree
591 force_rvalue (tree expr) in cvt.c
592 {
593 if (IS_AGGR_TYPE (TREE_TYPE (expr)) && TREE_CODE (expr) != TARGET_EXPR)
594 expr = ocp_convert (TREE_TYPE (expr), expr,
595 CONV_IMPLICIT|CONV_FORCE_TEMP, LOOKUP_NORMAL);
596 else
597 expr = decay_conversion (expr);
598
599 return expr;
600 }
We will see the detail of ocp_convert in short later. As summary here, the function will generate code for invoking the appropriate copy ctor and then invoking build_cplus_new to create the temporary with its initialization. Before leaving bot_manip , it worthes a look of build_target_expr .
234 static tree
235 build_target_expr (tree decl, tree value) in cp/tree.c
236 {
237 tree t;
238
239 t = build (TARGET_EXPR, TREE_TYPE (decl), decl, value,
240 cxx_maybe_build_cleanup (decl), NULL_TREE);
241 /* We always set TREE_SIDE_EFFECTS so that expand_expr does not
242 ignore the TARGET_EXPR. If there really turn out to be no
243 side-effects, then the optimizer should be able to get rid of
244 whatever code is generated anyhow. */
245 TREE_SIDE_EFFECTS (t) = 1;
246
247 return t;
248 }
For temporary with non-trivial destructor, the compiler needs generate code to destory the temporary at exitting its scope by invoking the destructor. So at line 240, cxx_maybe_build_cleanup generates this code if necessary.
Now at line 1213 in bot_manip , u is the TARGET_EXPR built corresponding to t . It maps the new version temporary with the old one. Then, we immediately replace the old one with it. However, it is possible some sub-nodes within node handled by break_out_target_exprs , still hold the reference to this old version, which needs be updated as below.
1236 static tree
1237 bot_replace (tree* t, in cp/tree.c
1238 int* walk_subtrees ATTRIBUTE_UNUSED ,
1239 void* data)
1240 {
1241 splay_tree target_remap = ((splay_tree) data);
1242
1243 if (TREE_CODE (*t) == VAR_DECL)
1244 {
1245 splay_tree_node n = splay_tree_lookup (target_remap,
1246 (splay_tree_key) *t);
1247 if (n)
1248 *t = (tree) n->value;
1249 }
1250
1251 return NULL_TREE;
1252 }
Back convert_default_arg , it gets the updated arg at line 4274 from break_out_target_exprs , then the following functions are used to generate code for the initialization.
5.12.5.2.2.2.3. Ellipsis arguments
The last possibility is the ellipsis arguments, note that ellipsis argument can’t coexist with default argument. In front-end to tell out function declaration containing ellipsis arguments, the list of parameters is terminated by NULL instead of void_list_node .
4161 tree
4162 convert_arg_to_ellipsis (tree arg) in call.c
4163 {
4164 /* [expr.call]
4165
4166 The lvalue-to-rvalue, array-to-pointer, and function-to-pointer
4167 standard conversions are performed. */
4168 arg = decay_conversion (arg);
4169 /* [expr.call]
4170
4171 If the argument has integral or enumeration type that is subject
4172 to the integral promotions (_conv.prom_), or a floating point
4173 type that is subject to the floating point promotion
4174 (_conv.fpprom_), the value of the argument is converted to the
4175 promoted type before the call. */
4176 if (TREE_CODE (TREE_TYPE (arg)) == REAL_TYPE
4177 && (TYPE_PRECISION (TREE_TYPE (arg))
4178 < TYPE_PRECISION (double_type_node)))
4179 arg = convert_to_real (double_type_node, arg);
4180 else if (INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (arg)))
4181 arg = perform_integral_promotions (arg);
4182
4183 arg = require_complete_type (arg);
4184
4185 if (arg != error_mark_node
4186 && !pod_type_p (TREE_TYPE (arg)))
4187 {
4188 /* Undefined behavior [expr.call] 5.2.2/7. We used to just warn
4189 here and do a bitwise copy, but now cp_expr_size will abort if we
4190 try to do that.
4191 If the call appears in the context of a sizeof expression,
4192 there is no need to emit a warning, since the expression won't be
4193 evaluated. We keep the builtin_trap just as a safety check. */
4194 if (!skip_evaluation )
4195 warning ("cannot pass objects of non-POD type `%#T' through `...'; "
4196 "call will abort at runtime", TREE_TYPE (arg));
4197 arg = call_builtin_trap ();
4198 arg = build (COMPOUND_EXPR, integer_type_node, arg,
4199 integer_zero_node);
4200 }
4201
4202 return arg;
4203 }
[3], clause 5.2.2 “Function call”, terms 7 defines the behavior of compiler upon ellipsis argument as below.
7. When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.7). The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9), the behavior is undefined. If the argument has integral or enumeration type that is subject to the integral promotions (4.5), or a floating point type that is subject to the floating point promotion (4.6), the value of the argument is converted to the promoted type before the call. These promotions are referred to as the default argument promotions . |
Function decay_conversion performs the conversions in exp, which are used when an lvalue appears in an rvalue context, and include lvalue-to-rvalue, array-to-pointer, and function-to-pointer conversion.
1335 tree
1336 decay_conversion (tree exp) n typeck.c
1337 {
1338 tree type;
1339 enum tree_code code;
1340
1341 type = TREE_TYPE (exp);
1342 code = TREE_CODE (type);
1343
1344 if (code == REFERENCE_TYPE)
1345 {
1346 exp = convert_from_reference (exp);
1347 type = TREE_TYPE (exp);
1348 code = TREE_CODE (type);
1349 }
1350
1351 if (type == error_mark_node)
1352 return error_mark_node;
1353
1354 if (type_unknown_p (exp))
1355 {
1356 cxx_incomplete_type_error (exp, TREE_TYPE (exp));
1357 return error_mark_node;
1358 }
1359
1360 /* Constants can be used directly unless they're not loadable. */
1361 if (TREE_CODE (exp) == CONST_DECL)
1362 exp = DECL_INITIAL (exp);
1363 /* Replace a nonvolatile const static variable with its value. We
1364 don't do this for arrays, though; we want the address of the
1365 first element of the array, not the address of the first element
1366 of its initializing constant. */
1367 else if (code != ARRAY_TYPE)
1368 {
1369 exp = decl_constant_value (exp);
1370 type = TREE_TYPE (exp);
1371 }
1372
1373 /* build_c_cast puts on a NOP_EXPR to make the result not an lvalue.
1374 Leave such NOP_EXPRs, since RHS is being used in non-lvalue context. */
1375
1376 if (code == VOID_TYPE)
1377 {
1378 error ("void value not ignored as it ought to be");
1379 return error_mark_node;
1380 }
1381 if (invalid_nonstatic_memfn_p (exp))
1382 return error_mark_node;
1383 if (code == FUNCTION_TYPE || is_overloaded_fn (exp))
1384 return build_unary_op (ADDR_EXPR, exp, 0);
1385 if (code == ARRAY_TYPE)
1386 {
1387 tree adr;
1388 tree ptrtype;
1389
1390 if (TREE_CODE (exp) == INDIRECT_REF)
1391 return build_nop (build_pointer_type (TREE_TYPE (type)),
1392 TREE_OPERAND (exp, 0));
1393
1394 if (TREE_CODE (exp) == COMPOUND_EXPR)
1395 {
1396 tree op1 = decay_conversion (TREE_OPERAND (exp, 1));
1397 return build (COMPOUND_EXPR, TREE_TYPE (op1),
1398 TREE_OPERAND (exp, 0), op1);
1399 }
1400
1401 if (!lvalue_p (exp)
1402 && ! (TREE_CODE (exp) == CONSTRUCTOR && TREE_STATIC (exp)))
1403 {
1404 error ("invalid use of non-lvalue array");
1405 return error_mark_node;
1406 }
1407
1408 ptrtype = build_pointer_type (TREE_TYPE (type));
1409
1410 if (TREE_CODE (exp) == VAR_DECL)
1411 {
1412 if (!cxx_mark_addressable (exp))
1413 return error_mark_node;
1414 adr = build_nop (ptrtype, build_address (exp));
1415 TREE_SIDE_EFFECTS (adr) = 0; /* Default would be, same as EXP. */
1416 return adr;
1417 }
1418 /* This way is better for a COMPONENT_REF since it can
1419 simplify the offset for a component. */
1420 adr = build_unary_op (ADDR_EXPR, exp, 1);
1421 return cp_convert (ptrtype, adr);
1422 }
1423
1424 /* [basic.lval]: Class rvalues can have cv-qualified types; non-class
1425 rvalues always have cv-unqualified types. */
1426 if (! CLASS_TYPE_P (type))
1427 exp = cp_convert (TYPE_MAIN_VARIANT (type), exp);
1428
1429 return exp;
1430 }
First for REFERENCE_TYPE, it is a lvalue, to change it to rvalue, it should uses the value referred instead of holding reference any more. In front-end, INDIRECT_REF is built for the purpose (it because reference always has its address passed just like pointer, and in compiler, the mode of reference is the same as pointer, both are ptr_mode , see build_reference_type ).
566 tree
567 convert_from_reference (tree val) in cvt.c
568 {
569 if (TREE_CODE (TREE_TYPE (val)) == REFERENCE_TYPE)
570 return build_indirect_ref (val, NULL);
571 return val;
572 }
Remember pointer is rvalue. When handling ARRAY_TYPE, condition at line 1390 is satisfied, if we are using expression like: int *a[i] (a is array having dimensions more than 1, for example, int A[2][2]), so it builds type like: int**, and see converting pointer of “int *a[i]” to “int**” needesn’t generate any code, NOP_EXPR is built for such conversion.
Further if exp simply is a declaration of an array, for exmaple: int a[8], rvalue of this declaration is just “int*”. It can build this rvalue directly. However, for other cases, for example: tempA.a (it is a tree rooted by SCOPE_REF), it is not a simple rule we can use directly, so it asks build_unary_op and cp_convert to do the appropriate conversion.
As comment at line 4188 in convert_arg_to_ellipsis mentions, for the undefined behavior of non-POD class type, GCC once uses bitwise copy, but this behavior will cause later invocation of cp_expr_size triggering abort in current version. This abort is triggered by below invocation of “__builtin_trap”, which is included in arg of COMPOUND_EXPR at line 4198.
4148 c tree
4149 call_builtin_trap (void) in call.c
4150 {
4151 tree fn = IDENTIFIER_GLOBAL_VALUE (get_identifier ("__builtin_trap"));
4152
4153 my_friendly_assert (fn != NULL, 20030927);
4154 fn = build_call (fn, NULL_TREE);
4155 return fn;
4156 }
The behavior of the builtin trap will use trap if the target machine defines it; otherwise the compiler will call abort () (see expand_builtin_trap ).
Below is an interesting test for the behavior of non-POD class type:
#include <stdarg.h>
class A {
public : virtual void func() {}
};
int func (int a, ...) {
va_list ap;
va_start(ap, a);
va_arg(ap, A);
va_end(ap);
return 1;
}
int main() {
A a;
func (1, a); // sizeof (func (1, a))
}
The compiler will give out following warning:
test2.cpp: In function ‘int func(int, ...)’:
test2.cpp:11: warning: cannot receive objects of non-POD type ‘class A’ through ‘...’; call will abort at runtime
test2.cpp: In function ‘int main()’:
test2.cpp:19: warning: cannot pass objects of non-POD type ‘class A’ through ’...’; call will abort at runtime
When execute the program, it gets error: Illegal instruction.
However, if we use statement in comment instead, the compiler then gives warning as:
test2.cpp: In function ‘int func(int, ...)’:
test2.cpp:11: warning: cannot receive objects of non-POD type ‘class A’ through ‘...’; call will abort at runtime
And at execution, no error will be output, as func(1, a) will not be evaluated except its returned value.