From patchwork Sun Mar 25 04:00:06 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jason Merrill X-Patchwork-Id: 148553 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) by ozlabs.org (Postfix) with SMTP id CF287B6EE6 for ; Sun, 25 Mar 2012 15:00:52 +1100 (EST) Comment: DKIM? See http://www.dkim.org DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=gcc.gnu.org; s=default; x=1333252854; h=Comment: DomainKey-Signature:Received:Received:Received:Received:Received: Message-ID:Date:From:User-Agent:MIME-Version:To:Subject: Content-Type:Mailing-List:Precedence:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:Sender:Delivered-To; bh=6thVFVe kNuJE0tlo9xcv8WoECkQ=; b=tL6HX2YyAfGdB9CKeI/FaLdhnorm2zdWZuh4QLt hDtiWyIN6w0YHJAQ/Rtnx2AmLuVEUfcprb1affId3b50ct8S0ZpIYzDuW/IIoNtN 0gt9NpVddfDQyrjrDHZ6l06pZHFitvqMHH2mrRpueGFKz4C+QZ9nlsaDCFa19enE sTn0= Comment: DomainKeys? See http://antispam.yahoo.com/domainkeys DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; s=default; d=gcc.gnu.org; h=Received:Received:X-SWARE-Spam-Status:X-Spam-Check-By:Received:Received:Received:Message-ID:Date:From:User-Agent:MIME-Version:To:Subject:Content-Type:Mailing-List:Precedence:List-Id:List-Unsubscribe:List-Archive:List-Post:List-Help:Sender:Delivered-To; b=kv4BT9SuPse1nux+HncjIRAmJNAdwQBeE7SPIdkiJ8Bf1wfhvxCvr4cayOM8s0 FfPMNZ1KaRnx7ZRER2UrvveEZRebJcw23ptd+7btnLe9YZOyJU8lzFSHk+ewUvIc M/FChm+FG0X7b/Z+nFNOhaPfguRCherIWv3Upqb/2nIMI=; Received: (qmail 16353 invoked by alias); 25 Mar 2012 04:00:42 -0000 Received: (qmail 16164 invoked by uid 22791); 25 Mar 2012 04:00:36 -0000 X-SWARE-Spam-Status: No, hits=-6.5 required=5.0 tests=AWL, BAYES_00, RCVD_IN_DNSWL_HI, SPF_HELO_PASS, TW_FN, TW_TM, T_RP_MATCHES_RCVD X-Spam-Check-By: sourceware.org Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Sun, 25 Mar 2012 04:00:11 +0000 Received: from int-mx02.intmail.prod.int.phx2.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id q2P40AgU010131 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Sun, 25 Mar 2012 00:00:10 -0400 Received: from [10.3.113.28] (ovpn-113-28.phx2.redhat.com [10.3.113.28]) by int-mx02.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id q2P407wW004770 for ; Sun, 25 Mar 2012 00:00:09 -0400 Message-ID: <4F6E9846.7060702@redhat.com> Date: Sun, 25 Mar 2012 00:00:06 -0400 From: Jason Merrill User-Agent: Mozilla/5.0 (X11; Linux i686; rv:10.0.1) Gecko/20120216 Thunderbird/10.0.1 MIME-Version: 1.0 To: gcc-patches List Subject: C++ PATCH to add auto return type deduction with -std=c++1y Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org As I mentioned in my patch to add -std=c++1y, I've been working on a proposal for the next standard to support return type deduction for normal functions, not just lambdas. This patch implements that proposal. I tried to send this message before with the proposal attached in HTML, but the mailing list rejects HTML attachments, so I've dropped it. I'm happy to send it separately to anyone interested. Tested x86_64-pc-linux-gnu, applying to trunk. commit c12876e8b8e5d4ee70b5ea24126eae501af24d3d Author: Jason Merrill Date: Tue Mar 20 20:13:28 2012 -0400 Implement return type deduction for normal functions with -std=c++1y. * cp-tree.h (FNDECL_USED_AUTO): New macro. (LAMBDA_EXPR_DEDUCE_RETURN_TYPE_P): Remove. (dependent_lambda_return_type_node): Remove. (CPTI_DEPENDENT_LAMBDA_RETURN_TYPE): Remove. (struct language_function): Add x_auto_return_pattern field. (current_function_auto_return_pattern): New. (enum tsubst_flags): Add tf_partial. * decl.c (decls_match): Handle auto return comparison. (duplicate_decls): Adjust error message for auto return. (cxx_init_decl_processing): Remove dependent_lambda_return_type_node. (cp_finish_decl): Don't do auto deduction for functions. (grokdeclarator): Allow auto return without trailing return type in C++1y mode. (check_function_type): Defer checking of deduced return type. (start_preparsed_function): Set current_function_auto_return_pattern. (finish_function): Set deduced return type to void if not previously deduced. * decl2.c (change_return_type): Handle error_mark_node. (mark_used): Always instantiate functions with deduced return type. Complain about use if deduction isn't done. * parser.c (cp_parser_lambda_declarator_opt): Use 'auto' for initial return type. (cp_parser_lambda_body): Don't deduce return type in a template. (cp_parser_conversion_type_id): Allow auto in C++1y. * pt.c (instantiate_class_template_1): Don't mess with LAMBDA_EXPR_DEDUCE_RETURN_TYPE_P. (tsubst_copy_and_build): Likewise. (fn_type_unification, tsubst): Don't reduce the template parm level of 'auto' during deduction. (unify): Compare 'auto' specially. (get_bindings): Change test. (always_instantiate_p): Always instantiate functions with deduced return type. (do_auto_deduction): Handle error_mark_node and lambda context. Don't check for use in initializer. (contains_auto_r): Remove. * search.c (lookup_conversions_r): Handle auto conversion function. * semantics.c (lambda_return_type): Handle null return. Don't mess with dependent_lambda_return_type_node. (apply_deduced_return_type): Rename from apply_lambda_return_type. * typeck.c (merge_types): Handle auto. (check_return_expr): Do auto deduction. * typeck2.c (add_exception_specifier): Fix complain check. diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index fc60d86..7d986a8 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -96,8 +96,8 @@ c-common.h, not after. DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL) STATEMENT_LIST_TRY_BLOCK (in STATEMENT_LIST) TYPENAME_IS_RESOLVING_P (in TYPE_NAME_TYPE) - LAMBDA_EXPR_DEDUCE_RETURN_TYPE_P (in LAMBDA_EXPR) TARGET_EXPR_DIRECT_INIT_P (in TARGET_EXPR) + FNDECL_USED_AUTO (in FUNCTION_DECL) 3: (TREE_REFERENCE_EXPR) (in NON_LVALUE_EXPR) (commented-out). ICS_BAD_FLAG (in _CONV) FN_TRY_BLOCK_P (in TRY_BLOCK) @@ -660,11 +660,6 @@ enum cp_lambda_default_capture_mode_type { #define LAMBDA_EXPR_MUTABLE_P(NODE) \ TREE_LANG_FLAG_1 (LAMBDA_EXPR_CHECK (NODE)) -/* True iff we should try to deduce the lambda return type from any return - statement. */ -#define LAMBDA_EXPR_DEDUCE_RETURN_TYPE_P(NODE) \ - TREE_LANG_FLAG_2 (LAMBDA_EXPR_CHECK (NODE)) - /* The return type in the expression. * NULL_TREE indicates that none was specified. */ #define LAMBDA_EXPR_RETURN_TYPE(NODE) \ @@ -804,7 +799,6 @@ enum cp_tree_index CPTI_CLASS_TYPE, CPTI_UNKNOWN_TYPE, CPTI_INIT_LIST_TYPE, - CPTI_DEPENDENT_LAMBDA_RETURN_TYPE, CPTI_VTBL_TYPE, CPTI_VTBL_PTR_TYPE, CPTI_STD, @@ -876,7 +870,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; #define class_type_node cp_global_trees[CPTI_CLASS_TYPE] #define unknown_type_node cp_global_trees[CPTI_UNKNOWN_TYPE] #define init_list_type_node cp_global_trees[CPTI_INIT_LIST_TYPE] -#define dependent_lambda_return_type_node cp_global_trees[CPTI_DEPENDENT_LAMBDA_RETURN_TYPE] #define vtbl_type_node cp_global_trees[CPTI_VTBL_TYPE] #define vtbl_ptr_type_node cp_global_trees[CPTI_VTBL_PTR_TYPE] #define std_node cp_global_trees[CPTI_STD] @@ -1076,6 +1069,7 @@ struct GTY(()) language_function { tree x_in_charge_parm; tree x_vtt_parm; tree x_return_value; + tree x_auto_return_pattern; BOOL_BITFIELD returns_value : 1; BOOL_BITFIELD returns_null : 1; @@ -1158,6 +1152,11 @@ struct GTY(()) language_function { #define current_function_return_value \ (cp_function_chain->x_return_value) +/* A type involving 'auto' to be used for return type deduction. */ + +#define current_function_auto_return_pattern \ + (cp_function_chain->x_auto_return_pattern) + /* True if NAME is the IDENTIFIER_NODE for an overloaded "operator new" or "operator delete". */ #define NEW_DELETE_OPNAME_P(NAME) \ @@ -3085,6 +3084,13 @@ more_aggr_init_expr_args_p (const aggr_init_expr_arg_iterator *iter) #define DECL_LOCAL_FUNCTION_P(NODE) \ DECL_LANG_FLAG_0 (FUNCTION_DECL_CHECK (NODE)) +/* True if NODE was declared with auto in its return type, but it has + started compilation and so the return type might have been changed by + return type deduction; its declared return type should be found in + DECL_STRUCT_FUNCTION(NODE)->language->x_auto_return_pattern. */ +#define FNDECL_USED_AUTO(NODE) \ + TREE_LANG_FLAG_2 (FUNCTION_DECL_CHECK (NODE)) + /* Nonzero if NODE is a DECL which we know about but which has not been explicitly declared, such as a built-in function or a friend declared inside a class. In the latter case DECL_HIDDEN_FRIEND_P @@ -4144,6 +4150,8 @@ enum tsubst_flags { conversion. */ tf_no_access_control = 1 << 7, /* Do not perform access checks, even when issuing other errors. */ + tf_partial = 1 << 8, /* Doing initial explicit argument + substitution in fn_type_unification. */ /* Convenient substitution flags combinations. */ tf_warning_or_error = tf_warning | tf_error }; @@ -5619,7 +5627,7 @@ extern tree lambda_capture_field_type (tree); extern tree lambda_return_type (tree); extern tree lambda_proxy_type (tree); extern tree lambda_function (tree); -extern void apply_lambda_return_type (tree, tree); +extern void apply_deduced_return_type (tree, tree); extern tree add_capture (tree, tree, tree, bool, bool); extern tree add_default_capture (tree, tree, tree); extern tree build_capture_proxy (tree); diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c index e664d43..f021edf 100644 --- a/gcc/cp/decl.c +++ b/gcc/cp/decl.c @@ -960,6 +960,7 @@ decls_match (tree newdecl, tree olddecl) tree f2 = TREE_TYPE (olddecl); tree p1 = TYPE_ARG_TYPES (f1); tree p2 = TYPE_ARG_TYPES (f2); + tree r2; /* Specializations of different templates are different functions even if they have the same type. */ @@ -988,7 +989,14 @@ decls_match (tree newdecl, tree olddecl) if (TREE_CODE (f1) != TREE_CODE (f2)) return 0; - if (same_type_p (TREE_TYPE (f1), TREE_TYPE (f2))) + /* A declaration with deduced return type should use its pre-deduction + type for declaration matching. */ + if (FNDECL_USED_AUTO (olddecl)) + r2 = DECL_STRUCT_FUNCTION (olddecl)->language->x_auto_return_pattern; + else + r2 = TREE_TYPE (f2); + + if (same_type_p (TREE_TYPE (f1), r2)) { if (!prototype_p (f2) && DECL_EXTERN_C_P (olddecl) && (DECL_BUILT_IN (olddecl) @@ -1486,7 +1494,11 @@ duplicate_decls (tree newdecl, tree olddecl, bool newdecl_is_friend) TYPE_ARG_TYPES (TREE_TYPE (olddecl)))) { error ("new declaration %q#D", newdecl); - error ("ambiguates old declaration %q+#D", olddecl); + if (FNDECL_USED_AUTO (olddecl)) + error_at (DECL_SOURCE_LOCATION (olddecl), "ambiguates old " + "declaration with deduced return type"); + else + error ("ambiguates old declaration %q+#D", olddecl); return error_mark_node; } else @@ -3644,10 +3656,6 @@ cxx_init_decl_processing (void) init_list_type_node = make_node (LANG_TYPE); record_unknown_type (init_list_type_node, "init list"); - dependent_lambda_return_type_node = make_node (LANG_TYPE); - record_unknown_type (dependent_lambda_return_type_node, - "undeduced lambda return type"); - { /* Make sure we get a unique function type, so we can give its pointer type a name. (This wins for gdb.) */ @@ -6008,8 +6016,8 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p, && (DECL_INITIAL (decl) || init)) DECL_INITIALIZED_IN_CLASS_P (decl) = 1; - auto_node = type_uses_auto (type); - if (auto_node) + if (TREE_CODE (decl) != FUNCTION_DECL + && (auto_node = type_uses_auto (type))) { tree d_init; if (init == NULL_TREE) @@ -9188,9 +9196,13 @@ grokdeclarator (const cp_declarator *declarator, { if (!declarator->u.function.late_return_type) { - error ("%qs function uses % type specifier without" - " trailing return type", name); - return error_mark_node; + if (current_class_type + && LAMBDA_TYPE_P (current_class_type)) + /* OK for C++11 lambdas. */; + else if (cxx_dialect < cxx1y) + pedwarn (input_location, 0, "%qs function uses " + "% type specifier without trailing " + "return type", name); } else if (!is_auto (type)) { @@ -10029,7 +10041,8 @@ grokdeclarator (const cp_declarator *declarator, } else if (decl_context == FIELD) { - if (!staticp && type_uses_auto (type)) + if (!staticp && TREE_CODE (type) != METHOD_TYPE + && type_uses_auto (type)) { error ("non-static data member declared %"); type = error_mark_node; @@ -12570,7 +12583,8 @@ check_function_type (tree decl, tree current_function_parms) /* In a function definition, arg types must be complete. */ require_complete_types_for_parms (current_function_parms); - if (dependent_type_p (return_type)) + if (dependent_type_p (return_type) + || type_uses_auto (return_type)) return; if (!COMPLETE_OR_VOID_TYPE_P (return_type) || (TYPE_FOR_JAVA (return_type) && MAYBE_CLASS_TYPE_P (return_type))) @@ -12741,6 +12755,7 @@ start_preparsed_function (tree decl1, tree attrs, int flags) /* Build the return declaration for the function. */ restype = TREE_TYPE (fntype); + if (DECL_RESULT (decl1) == NULL_TREE) { tree resdecl; @@ -12849,6 +12864,12 @@ start_preparsed_function (tree decl1, tree attrs, int flags) current_stmt_tree ()->stmts_are_full_exprs_p = 1; current_binding_level = bl; + if (!processing_template_decl && type_uses_auto (restype)) + { + FNDECL_USED_AUTO (decl1) = true; + current_function_auto_return_pattern = restype; + } + /* Start the statement-tree, start the tree now. */ DECL_SAVED_TREE (decl1) = push_stmt_list (); @@ -13463,6 +13484,23 @@ finish_function (int flags) of curly braces for a function. */ gcc_assert (stmts_are_full_exprs_p ()); + /* If there are no return statements in a function with auto return type, + the return type is void. But if the declared type is something like + auto*, this is an error. */ + if (!processing_template_decl && FNDECL_USED_AUTO (fndecl) + && TREE_TYPE (fntype) == current_function_auto_return_pattern) + { + if (!is_auto (current_function_auto_return_pattern) + && !current_function_returns_value && !current_function_returns_null) + { + error ("no return statements in function returning %qT", + current_function_auto_return_pattern); + inform (input_location, "only plain % return type can be " + "deduced to %"); + } + apply_deduced_return_type (fndecl, void_type_node); + } + /* Save constexpr function body before it gets munged by the NRV transformation. */ maybe_save_function_definition (fndecl); diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c index 7eccf67..b048ac7 100644 --- a/gcc/cp/decl2.c +++ b/gcc/cp/decl2.c @@ -151,6 +151,9 @@ change_return_type (tree new_ret, tree fntype) tree raises = TYPE_RAISES_EXCEPTIONS (fntype); tree attrs = TYPE_ATTRIBUTES (fntype); + if (new_ret == error_mark_node) + return fntype; + if (same_type_p (new_ret, TREE_TYPE (fntype))) return fntype; @@ -4281,7 +4284,11 @@ mark_used (tree decl) if ((TREE_CODE (decl) != VAR_DECL && TREE_CODE (decl) != FUNCTION_DECL) || DECL_LANG_SPECIFIC (decl) == NULL || DECL_THUNK_P (decl)) - return true; + { + if (!processing_template_decl && type_uses_auto (TREE_TYPE (decl))) + error ("use of %qD before deduction of %", decl); + return true; + } /* We only want to do this processing once. We don't need to keep trying to instantiate inline templates, because unit-at-a-time will make sure @@ -4303,10 +4310,13 @@ mark_used (tree decl) /* Normally, we can wait until instantiation-time to synthesize DECL. However, if DECL is a static data member initialized with a constant or a constexpr function, we need it right now because a reference to - such a data member or a call to such function is not value-dependent. */ + such a data member or a call to such function is not value-dependent. + For a function that uses auto in the return type, we need to instantiate + it to find out its type. */ if ((decl_maybe_constant_var_p (decl) || (TREE_CODE (decl) == FUNCTION_DECL - && DECL_DECLARED_CONSTEXPR_P (decl))) + && (DECL_DECLARED_CONSTEXPR_P (decl) + || type_uses_auto (TREE_TYPE (TREE_TYPE (decl)))))) && DECL_LANG_SPECIFIC (decl) && DECL_TEMPLATE_INFO (decl) && !uses_template_parms (DECL_TI_ARGS (decl))) @@ -4321,6 +4331,9 @@ mark_used (tree decl) --function_depth; } + if (type_uses_auto (TREE_TYPE (decl))) + error ("use of %qD before deduction of %", decl); + /* If we don't need a value, then we don't need to synthesize DECL. */ if (cp_unevaluated_operand != 0) return true; diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 75b7bdb..eac60f1 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -8416,9 +8416,8 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr) if (LAMBDA_EXPR_RETURN_TYPE (lambda_expr)) return_type_specs.type = LAMBDA_EXPR_RETURN_TYPE (lambda_expr); else - /* Maybe we will deduce the return type later, but we can use void - as a placeholder return type anyways. */ - return_type_specs.type = void_type_node; + /* Maybe we will deduce the return type later. */ + return_type_specs.type = make_auto (); p = obstack_alloc (&declarator_obstack, 0); @@ -8539,7 +8538,8 @@ cp_parser_lambda_body (cp_parser* parser, tree lambda_expr) if (cp_parser_parse_definitely (parser)) { - apply_lambda_return_type (lambda_expr, lambda_return_type (expr)); + if (!processing_template_decl) + apply_deduced_return_type (fco, lambda_return_type (expr)); /* Will get error here if type not deduced yet. */ finish_return_stmt (expr); @@ -8550,13 +8550,10 @@ cp_parser_lambda_body (cp_parser* parser, tree lambda_expr) if (!done) { - if (!LAMBDA_EXPR_RETURN_TYPE (lambda_expr)) - LAMBDA_EXPR_DEDUCE_RETURN_TYPE_P (lambda_expr) = true; while (cp_lexer_next_token_is_keyword (parser->lexer, RID_LABEL)) cp_parser_label_declaration (parser); cp_parser_statement_seq_opt (parser, NULL_TREE); cp_parser_require (parser, CPP_CLOSE_BRACE, RT_CLOSE_BRACE); - LAMBDA_EXPR_DEDUCE_RETURN_TYPE_P (lambda_expr) = false; } finish_compound_stmt (compound_stmt); @@ -11275,8 +11272,14 @@ cp_parser_conversion_type_id (cp_parser* parser) if (! cp_parser_uncommitted_to_tentative_parse_p (parser) && type_uses_auto (type_specified)) { - error ("invalid use of % in conversion operator"); - return error_mark_node; + if (cxx_dialect < cxx1y) + { + error ("invalid use of % in conversion operator"); + return error_mark_node; + } + else if (template_parm_scope_p ()) + warning (0, "use of % in member template " + "conversion operator can never be deduced"); } return type_specified; diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index b36e49d..f128947 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -9112,12 +9112,6 @@ instantiate_class_template_1 (tree type) tree decl = lambda_function (type); if (decl) { - tree lambda = CLASSTYPE_LAMBDA_EXPR (type); - if (LAMBDA_EXPR_DEDUCE_RETURN_TYPE_P (lambda)) - { - apply_lambda_return_type (lambda, void_type_node); - LAMBDA_EXPR_RETURN_TYPE (lambda) = NULL_TREE; - } instantiate_decl (decl, false, false); maybe_add_lambda_conv_op (type); } @@ -11331,6 +11325,12 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) about the template parameter in question. */ return t; + /* Early in template argument deduction substitution, we don't + want to reduce the level of 'auto', or it will be confused + with a normal template parm in subsequent deduction. */ + if (is_auto (t) && (complain & tf_partial)) + return t; + /* If we get here, we must have been looking at a parm for a more deeply nested template. Make a new version of this template parameter, but with a lower level. */ @@ -14334,14 +14334,8 @@ tsubst_copy_and_build (tree t, = (LAMBDA_EXPR_DISCRIMINATOR (t)); LAMBDA_EXPR_EXTRA_SCOPE (r) = RECUR (LAMBDA_EXPR_EXTRA_SCOPE (t)); - if (LAMBDA_EXPR_RETURN_TYPE (t) == dependent_lambda_return_type_node) - { - LAMBDA_EXPR_RETURN_TYPE (r) = dependent_lambda_return_type_node; - LAMBDA_EXPR_DEDUCE_RETURN_TYPE_P (r) = true; - } - else - LAMBDA_EXPR_RETURN_TYPE (r) - = tsubst (LAMBDA_EXPR_RETURN_TYPE (t), args, complain, in_decl); + LAMBDA_EXPR_RETURN_TYPE (r) + = tsubst (LAMBDA_EXPR_RETURN_TYPE (t), args, complain, in_decl); gcc_assert (LAMBDA_EXPR_THIS_CAPTURE (t) == NULL_TREE && LAMBDA_EXPR_PENDING_PROXIES (t) == NULL); @@ -14860,7 +14854,7 @@ fn_type_unification (tree fn, fntype = deduction_tsubst_fntype (fn, converted_args, (explain_p ? tf_warning_or_error - : tf_none)); + : tf_none) | tf_partial); processing_template_decl -= incomplete; if (fntype == error_mark_node) @@ -16275,7 +16269,8 @@ unify (tree tparms, tree targs, tree parm, tree arg, int strict, to see if it matches ARG. */ { if (TREE_CODE (arg) == TREE_CODE (parm) - && same_type_p (parm, arg)) + && (is_auto (parm) ? is_auto (arg) + : same_type_p (parm, arg))) return unify_success (explain_p); else return unify_type_mismatch (explain_p, parm, arg); @@ -17408,7 +17403,7 @@ get_bindings (tree fn, tree decl, tree explicit_args, bool check_rettype) The call to fn_type_unification will handle substitution into the FN. */ decl_type = TREE_TYPE (decl); - if (explicit_args && uses_template_parms (decl_type)) + if (explicit_args && decl == DECL_TEMPLATE_RESULT (fn)) { tree tmpl; tree converted_args; @@ -18315,7 +18310,8 @@ always_instantiate_p (tree decl) that for "extern template" functions. Therefore, we check DECL_DECLARED_INLINE_P, rather than possibly_inlined_p. */ return ((TREE_CODE (decl) == FUNCTION_DECL - && DECL_DECLARED_INLINE_P (decl)) + && (DECL_DECLARED_INLINE_P (decl) + || type_uses_auto (TREE_TYPE (TREE_TYPE (decl))))) /* And we need to instantiate static data members so that their initializers are available in integral constant expressions. */ @@ -20269,20 +20265,6 @@ listify_autos (tree type, tree auto_node) return tsubst (type, argvec, tf_warning_or_error, NULL_TREE); } -/* walk_tree helper for do_auto_deduction. */ - -static tree -contains_auto_r (tree *tp, int *walk_subtrees ATTRIBUTE_UNUSED, - void *type) -{ - /* Is this a variable with the type we're looking for? */ - if (DECL_P (*tp) - && TREE_TYPE (*tp) == type) - return *tp; - else - return NULL_TREE; -} - /* Replace occurrences of 'auto' in TYPE with the appropriate type deduced from INIT. AUTO_NODE is the TEMPLATE_TYPE_PARM used for 'auto' in TYPE. */ @@ -20291,25 +20273,17 @@ do_auto_deduction (tree type, tree init, tree auto_node) { tree parms, tparms, targs; tree args[1]; - tree decl; int val; + if (init == error_mark_node) + return error_mark_node; + if (processing_template_decl && (TREE_TYPE (init) == NULL_TREE || BRACE_ENCLOSED_INITIALIZER_P (init))) /* Not enough information to try this yet. */ return type; - /* The name of the object being declared shall not appear in the - initializer expression. */ - decl = cp_walk_tree_without_duplicates (&init, contains_auto_r, type); - if (decl) - { - error ("variable %q#D with % type used in its own " - "initializer", decl); - return error_mark_node; - } - /* [dcl.spec.auto]: Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list (8.5.4), with @@ -20337,7 +20311,13 @@ do_auto_deduction (tree type, tree init, tree auto_node) /* If type is error_mark_node a diagnostic must have been emitted by now. Also, having a mention to '' in the diagnostic is not really useful to the user. */ - error ("unable to deduce %qT from %qE", type, init); + { + if (cfun && auto_node == current_function_auto_return_pattern + && LAMBDA_FUNCTION_P (current_function_decl)) + error ("unable to deduce lambda return type from %qE", init); + else + error ("unable to deduce %qT from %qE", type, init); + } return error_mark_node; } @@ -20348,8 +20328,14 @@ do_auto_deduction (tree type, tree init, tree auto_node) if (TREE_TYPE (auto_node) && !same_type_p (TREE_TYPE (auto_node), TREE_VEC_ELT (targs, 0))) { - error ("inconsistent deduction for %qT: %qT and then %qT", - auto_node, TREE_TYPE (auto_node), TREE_VEC_ELT (targs, 0)); + if (cfun && auto_node == current_function_auto_return_pattern + && LAMBDA_FUNCTION_P (current_function_decl)) + error ("inconsistent types %qT and %qT deduced for " + "lambda return type", TREE_TYPE (auto_node), + TREE_VEC_ELT (targs, 0)); + else + error ("inconsistent deduction for %qT: %qT and then %qT", + auto_node, TREE_TYPE (auto_node), TREE_VEC_ELT (targs, 0)); return error_mark_node; } TREE_TYPE (auto_node) = TREE_VEC_ELT (targs, 0); diff --git a/gcc/cp/search.c b/gcc/cp/search.c index bd1bc57..14d272e 100644 --- a/gcc/cp/search.c +++ b/gcc/cp/search.c @@ -2430,6 +2430,11 @@ lookup_conversions_r (tree binfo, if (!IDENTIFIER_MARKED (name)) { tree type = DECL_CONV_FN_TYPE (cur); + if (type_uses_auto (type)) + { + mark_used (cur); + type = DECL_CONV_FN_TYPE (cur); + } if (check_hidden_convs (binfo, virtual_depth, virtualness, type, parent_convs, other_convs)) diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c index 5646fa7..6294e19 100644 --- a/gcc/cp/semantics.c +++ b/gcc/cp/semantics.c @@ -8691,18 +8691,16 @@ begin_lambda_type (tree lambda) tree lambda_return_type (tree expr) { - tree type; + if (expr == NULL_TREE) + return void_type_node; if (type_unknown_p (expr) || BRACE_ENCLOSED_INITIALIZER_P (expr)) { cxx_incomplete_type_error (expr, TREE_TYPE (expr)); return void_type_node; } - if (type_dependent_expression_p (expr)) - type = dependent_lambda_return_type_node; - else - type = cv_unqualified (type_decays_to (unlowered_expr_type (expr))); - return type; + gcc_checking_assert (!type_dependent_expression_p (expr)); + return cv_unqualified (type_decays_to (unlowered_expr_type (expr))); } /* Given a LAMBDA_EXPR or closure type LAMBDA, return the op() of the @@ -8749,29 +8747,32 @@ lambda_capture_field_type (tree expr) return type; } -/* Recompute the return type for LAMBDA with body of the form: - { return EXPR ; } */ +/* Insert the deduced return type for an auto function. */ void -apply_lambda_return_type (tree lambda, tree return_type) +apply_deduced_return_type (tree fco, tree return_type) { - tree fco = lambda_function (lambda); tree result; - LAMBDA_EXPR_RETURN_TYPE (lambda) = return_type; - if (return_type == error_mark_node) return; - if (TREE_TYPE (TREE_TYPE (fco)) == return_type) - return; - /* TREE_TYPE (FUNCTION_DECL) == METHOD_TYPE - TREE_TYPE (METHOD_TYPE) == return-type */ + if (LAMBDA_FUNCTION_P (fco)) + { + tree lambda = CLASSTYPE_LAMBDA_EXPR (current_class_type); + LAMBDA_EXPR_RETURN_TYPE (lambda) = return_type; + } + + if (DECL_CONV_FN_P (fco)) + DECL_NAME (fco) = mangle_conv_op_name_for_type (return_type); + TREE_TYPE (fco) = change_return_type (return_type, TREE_TYPE (fco)); result = DECL_RESULT (fco); if (result == NULL_TREE) return; + if (TREE_TYPE (result) == return_type) + return; /* We already have a DECL_RESULT from start_preparsed_function. Now we need to redo the work it and allocate_struct_function @@ -8786,12 +8787,13 @@ apply_lambda_return_type (tree lambda, tree return_type) DECL_RESULT (fco) = result; - if (!processing_template_decl && aggregate_value_p (result, fco)) + if (!processing_template_decl) { + bool aggr = aggregate_value_p (result, fco); #ifdef PCC_STATIC_STRUCT_RETURN - cfun->returns_pcc_struct = 1; + cfun->returns_pcc_struct = aggr; #endif - cfun->returns_struct = 1; + cfun->returns_struct = aggr; } } diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c index d2d6c4e..b68de52 100644 --- a/gcc/cp/typeck.c +++ b/gcc/cp/typeck.c @@ -733,6 +733,11 @@ merge_types (tree t1, tree t2) if (t2 == error_mark_node) return t1; + /* Handle merging an auto redeclaration with a previous deduced + return type. */ + if (is_auto (t1)) + return t2; + /* Merge the attributes. */ attributes = (*targetm.merge_type_attributes) (t1, t2); @@ -7779,9 +7784,11 @@ tree check_return_expr (tree retval, bool *no_warning) { tree result; - /* The type actually returned by the function, after any - promotions. */ + /* The type actually returned by the function. */ tree valtype; + /* The type the function is declared to return, or void if + the declared type is incomplete. */ + tree functype; int fn_returns_value_p; bool named_return_value_okay_p; @@ -7812,30 +7819,6 @@ check_return_expr (tree retval, bool *no_warning) return NULL_TREE; } - /* As an extension, deduce lambda return type from a return statement - anywhere in the body. */ - if (retval && LAMBDA_FUNCTION_P (current_function_decl)) - { - tree lambda = CLASSTYPE_LAMBDA_EXPR (current_class_type); - if (LAMBDA_EXPR_DEDUCE_RETURN_TYPE_P (lambda)) - { - tree type = lambda_return_type (retval); - tree oldtype = LAMBDA_EXPR_RETURN_TYPE (lambda); - - if (oldtype == NULL_TREE) - apply_lambda_return_type (lambda, type); - /* If one of the answers is type-dependent, we can't do any - better until instantiation time. */ - else if (oldtype == dependent_lambda_return_type_node) - /* Leave it. */; - else if (type == dependent_lambda_return_type_node) - apply_lambda_return_type (lambda, type); - else if (!same_type_p (type, oldtype)) - error ("inconsistent types %qT and %qT deduced for " - "lambda return type", type, oldtype); - } - } - if (processing_template_decl) { current_function_returns_value = 1; @@ -7844,6 +7827,42 @@ check_return_expr (tree retval, bool *no_warning) return retval; } + functype = TREE_TYPE (TREE_TYPE (current_function_decl)); + + /* Deduce auto return type from a return statement. */ + if (current_function_auto_return_pattern) + { + tree auto_node; + tree type; + + if (!retval && !is_auto (current_function_auto_return_pattern)) + { + /* Give a helpful error message. */ + error ("return-statement with no value, in function returning %qT", + current_function_auto_return_pattern); + inform (input_location, "only plain % return type can be " + "deduced to %"); + type = error_mark_node; + } + else + { + if (!retval) + retval = void_zero_node; + auto_node = type_uses_auto (current_function_auto_return_pattern); + type = do_auto_deduction (current_function_auto_return_pattern, + retval, auto_node); + } + + if (type == error_mark_node) + /* Leave it. */; + else if (functype == current_function_auto_return_pattern) + apply_deduced_return_type (current_function_decl, type); + else + /* A mismatch should have been diagnosed in do_auto_deduction. */ + gcc_assert (same_type_p (type, functype)); + functype = type; + } + /* When no explicit return-value is given in a function with a named return value, the named return value is used. */ result = DECL_RESULT (current_function_decl); @@ -7857,12 +7876,11 @@ check_return_expr (tree retval, bool *no_warning) that's supposed to return a value. */ if (!retval && fn_returns_value_p) { - permerror (input_location, "return-statement with no value, in function returning %qT", - valtype); - /* Clear this, so finish_function won't say that we reach the - end of a non-void function (which we don't, we gave a - return!). */ - current_function_returns_null = 0; + if (functype != error_mark_node) + permerror (input_location, "return-statement with no value, in " + "function returning %qT", valtype); + /* Remember that this function did return. */ + current_function_returns_value = 1; /* And signal caller that TREE_NO_WARNING should be set on the RETURN_EXPR to avoid control reaches end of non-void function warnings in tree-cfg.c. */ @@ -7963,14 +7981,12 @@ check_return_expr (tree retval, bool *no_warning) && DECL_CONTEXT (retval) == current_function_decl && ! TREE_STATIC (retval) && ! DECL_ANON_UNION_VAR_P (retval) - && (DECL_ALIGN (retval) - >= DECL_ALIGN (DECL_RESULT (current_function_decl))) + && (DECL_ALIGN (retval) >= DECL_ALIGN (result)) /* The cv-unqualified type of the returned value must be the same as the cv-unqualified return type of the function. */ && same_type_p ((TYPE_MAIN_VARIANT (TREE_TYPE (retval))), - (TYPE_MAIN_VARIANT - (TREE_TYPE (TREE_TYPE (current_function_decl))))) + (TYPE_MAIN_VARIANT (functype))) /* And the returned value must be non-volatile. */ && ! TYPE_VOLATILE (TREE_TYPE (retval))); @@ -7995,8 +8011,6 @@ check_return_expr (tree retval, bool *no_warning) ; else { - /* The type the function is declared to return. */ - tree functype = TREE_TYPE (TREE_TYPE (current_function_decl)); int flags = LOOKUP_NORMAL | LOOKUP_ONLYCONVERTING; /* The functype's return type will have been set to void, if it @@ -8016,10 +8030,9 @@ check_return_expr (tree retval, bool *no_warning) && DECL_CONTEXT (retval) == current_function_decl && !TREE_STATIC (retval) && same_type_p ((TYPE_MAIN_VARIANT (TREE_TYPE (retval))), - (TYPE_MAIN_VARIANT - (TREE_TYPE (TREE_TYPE (current_function_decl))))) + (TYPE_MAIN_VARIANT (functype))) /* This is only interesting for class type. */ - && CLASS_TYPE_P (TREE_TYPE (TREE_TYPE (current_function_decl)))) + && CLASS_TYPE_P (functype)) flags = flags | LOOKUP_PREFER_RVALUE; /* First convert the value to the function's return type, then diff --git a/gcc/cp/typeck2.c b/gcc/cp/typeck2.c index 974f92f..80a1d04 100644 --- a/gcc/cp/typeck2.c +++ b/gcc/cp/typeck2.c @@ -1818,7 +1818,8 @@ add_exception_specifier (tree list, tree spec, int complain) else diag_type = DK_ERROR; /* error */ - if (diag_type != DK_UNSPECIFIED && complain) + if (diag_type != DK_UNSPECIFIED + && (complain & tf_warning_or_error)) cxx_incomplete_type_diagnostic (NULL_TREE, core, diag_type); return list; diff --git a/gcc/testsuite/g++.dg/cpp0x/auto18.C b/gcc/testsuite/g++.dg/cpp0x/auto18.C index 17f7f99..0a59242 100644 --- a/gcc/testsuite/g++.dg/cpp0x/auto18.C +++ b/gcc/testsuite/g++.dg/cpp0x/auto18.C @@ -2,5 +2,5 @@ void f() { - auto val = val; // { dg-error "auto. type used in its own initializer" } + auto val = val; // { dg-error "auto" } } diff --git a/gcc/testsuite/g++.dg/cpp0x/auto3.C b/gcc/testsuite/g++.dg/cpp0x/auto3.C index 860790d..2b51d31 100644 --- a/gcc/testsuite/g++.dg/cpp0x/auto3.C +++ b/gcc/testsuite/g++.dg/cpp0x/auto3.C @@ -1,5 +1,5 @@ // Negative test for auto -// { dg-options "-std=c++0x" } +// { dg-do compile { target c++11 } } #include @@ -10,7 +10,7 @@ auto x; // { dg-error "auto" } auto i = 42, j = 42.0; // { dg-error "auto" } // New CWG issue -auto a[2] = { 1, 2 }; // { dg-error "initializer_list" } +auto a[2] = { 1, 2 }; // { dg-error "auto|initializer_list" } template struct A { }; diff --git a/gcc/testsuite/g++.dg/cpp0x/trailing2.C b/gcc/testsuite/g++.dg/cpp0x/trailing2.C index 5f5af22..91e5557 100644 --- a/gcc/testsuite/g++.dg/cpp0x/trailing2.C +++ b/gcc/testsuite/g++.dg/cpp0x/trailing2.C @@ -1,6 +1,6 @@ // PR c++/37967 // Negative test for auto -// { dg-options "-std=c++0x" } +// { dg-do compile { target c++11 } } auto f1 () -> int; auto f2 (); // { dg-error "without trailing return type" } diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn1.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn1.C new file mode 100644 index 0000000..eb54149 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn1.C @@ -0,0 +1,5 @@ +// { dg-options -std=c++1y } + +constexpr auto f() { return (char)42; } +#define SA(X) static_assert ((X),#X) +SA (f() == 42); diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn10.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn10.C new file mode 100644 index 0000000..e3ed3a9 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn10.C @@ -0,0 +1,16 @@ +// A template declared with auto should be declared with auto in an +// explicit instantiation or explicit specialization, too. +// { dg-options -std=c++1y } + +template +auto f(T t) { return t; } + +template<> auto f(int); +template auto f(float); +template<> auto f(int*); +template auto f(float*); + +template<> short f(short); // { dg-error "does not match" } +template char f(char); // { dg-error "does not match" } +template<> short f(short*); // { dg-error "does not match" } +template char f(char*); // { dg-error "does not match" } diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn11.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn11.C new file mode 100644 index 0000000..a9984aa --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn11.C @@ -0,0 +1,5 @@ +// { dg-options -std=c++1y } + +auto f() { return; } // OK, return type is void +auto* g() { return; } // { dg-error "no value" } +auto* h() { } // { dg-error "no return statements" } diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn12.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn12.C new file mode 100644 index 0000000..e4e58e8 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn12.C @@ -0,0 +1,14 @@ +// { dg-options -std=c++1y } +// { dg-final { scan-assembler "_ZN1AIiEcviEv" } } + +template +struct A { + T t; + operator auto() { return t+1; } +}; + +int main() +{ + int i = A{42}; + return (i != 43); +} diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn13.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn13.C new file mode 100644 index 0000000..34a61ae --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn13.C @@ -0,0 +1,6 @@ +// { dg-options -std=c++1y } + +struct A { + template + operator auto() { return T(); } // { dg-warning "auto.*template" } +}; diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn2.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn2.C new file mode 100644 index 0000000..4c2cee7 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn2.C @@ -0,0 +1,3 @@ +// { dg-options -std=c++1y } + +auto f() { return f(); } // { dg-error "auto" } diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn3.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn3.C new file mode 100644 index 0000000..107c37f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn3.C @@ -0,0 +1,10 @@ +// { dg-options -std=c++1y } + +bool b; +auto f() +{ + if (b) + return 42; + else + return f(); +} diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn4.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn4.C new file mode 100644 index 0000000..0b76bfc --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn4.C @@ -0,0 +1,7 @@ +// { dg-options -std=c++1y } + +template +constexpr auto f(T t) { return t+1; } + +#define SA(X) static_assert((X),#X) +SA(f(1)==2); diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn5.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn5.C new file mode 100644 index 0000000..f9af6c2 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn5.C @@ -0,0 +1,11 @@ +// { dg-options -std=c++1y } +// { dg-do run } + +int i; +auto& f() { return i; } + +int main() +{ + f() = 42; + return i != 42; +} diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn6.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn6.C new file mode 100644 index 0000000..03ff537 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn6.C @@ -0,0 +1,18 @@ +// { dg-options -std=c++1y } + +template struct ST; +template struct ST {}; + +int g(int); +char& g(char); +double&& g(double); + +template auto&& f(T t) +{ return g(t); } // { dg-warning "reference to temporary" } + +int main() +{ + ST(); + ST(); + ST(); +} diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn7.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn7.C new file mode 100644 index 0000000..b915352 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn7.C @@ -0,0 +1,5 @@ +// { dg-options "-std=c++1y -pedantic-errors" } + +auto f(); + +template auto f(T); diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn8.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn8.C new file mode 100644 index 0000000..dcec899 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn8.C @@ -0,0 +1,13 @@ +// { dg-options "-std=c++1y -pedantic-errors" } + +auto f() { return 42; } // { dg-error "deduced return type" } +auto f(); // OK +int f(); // { dg-error "new declaration" } + +template auto f(T t) { return t; } +template T f(T t); + +int main() +{ + f(42); // { dg-error "ambiguous" } +} diff --git a/gcc/testsuite/g++.dg/cpp1y/auto-fn9.C b/gcc/testsuite/g++.dg/cpp1y/auto-fn9.C new file mode 100644 index 0000000..1fa7479 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/auto-fn9.C @@ -0,0 +1,11 @@ +// { dg-options -std=c++1y } +// { dg-final { scan-assembler "_Z1fIiERDaRKT_S1_" } } + +template +auto& f(const T& t, T u) { return t; } + +int main() +{ + int i; + f(i,i); +} diff --git a/gcc/testsuite/g++.dg/gomp/pr38639.C b/gcc/testsuite/g++.dg/gomp/pr38639.C index e7145ff..481583e 100644 --- a/gcc/testsuite/g++.dg/gomp/pr38639.C +++ b/gcc/testsuite/g++.dg/gomp/pr38639.C @@ -6,7 +6,7 @@ template void foo () { #pragma omp parallel for - for (auto i = i = 0; i<4; ++i) // { dg-error "incomplete|unable|invalid" } + for (auto i = i = 0; i<4; ++i) // { dg-error "incomplete|unable|invalid|auto" } ; } diff --git a/gcc/testsuite/g++.dg/warn/pr23075.C b/gcc/testsuite/g++.dg/warn/pr23075.C index e5b1b48..59e93be 100644 --- a/gcc/testsuite/g++.dg/warn/pr23075.C +++ b/gcc/testsuite/g++.dg/warn/pr23075.C @@ -6,4 +6,4 @@ int foo (void) { return; // { dg-error "with no value" } -} // { dg-warning "no return statement" } +} // { dg-bogus "no return statement" } diff --git a/gcc/testsuite/g++.old-deja/g++.pt/spec22.C b/gcc/testsuite/g++.old-deja/g++.pt/spec22.C index 41aab39..94bffdb 100644 --- a/gcc/testsuite/g++.old-deja/g++.pt/spec22.C +++ b/gcc/testsuite/g++.old-deja/g++.pt/spec22.C @@ -10,6 +10,6 @@ struct S template template <> // { dg-error "enclosing class templates|invalid explicit specialization" } -void S::f () // { dg-error "does not match|invalid function declaration" } +void S::f () { }