From patchwork Fri Oct 2 16:55:11 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nathan Sidwell X-Patchwork-Id: 1375949 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=sourceware.org; envelope-from=gcc-patches-bounces@gcc.gnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=acm.org Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20161025 header.b=K0h9K/J4; dkim-atps=neutral Received: from sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4C2x212Xjfz9sSn for ; Sat, 3 Oct 2020 02:55:19 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 803EE398E451; Fri, 2 Oct 2020 16:55:17 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from mail-qt1-x82b.google.com (mail-qt1-x82b.google.com [IPv6:2607:f8b0:4864:20::82b]) by sourceware.org (Postfix) with ESMTPS id 51790398D031 for ; Fri, 2 Oct 2020 16:55:14 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org 51790398D031 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=acm.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=nathanmsidwell@gmail.com Received: by mail-qt1-x82b.google.com with SMTP id m9so1137463qth.7 for ; Fri, 02 Oct 2020 09:55:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:to:from:subject:message-id:date:user-agent:mime-version :content-language; bh=nmii8NfF+byEv698suzUdBMZwgR02X43CgLH8zTYYeE=; b=K0h9K/J4VAfqKSxh4Fglfv8hM+sE43riPRU3uXcSZFeRotIXJ/MIb1WblD3lgv2Ey9 x2cHv0KUAeotbmVE/XpeCBsEJa0I4hwEN/gsgsArQwzuDrKe5o4v0DLeGSLH6KTmnpJe Hs1YfAfNZEqKgejLf/aKUsFEXuLls9KSjZvg8Dg5cyqCc61JZ0vDbKhE5XRUc/W3vQZV p+dSOkd+C0KNmfRDgu/TWVJH0Wu68pPtYmM8PxHThkgK2juBya5wU+x5kUZJpQSIuOtl GRELpIn3uWBwqWVpHWyBrm0ikBfbeXWEoH84A/Vx4LKRYELjWcSqyLxXC3S+l8UzPCeB c7ug== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:to:from:subject:message-id:date :user-agent:mime-version:content-language; bh=nmii8NfF+byEv698suzUdBMZwgR02X43CgLH8zTYYeE=; b=NZPX6uF0u3AhUv4UF5KBJz+g4LSRQ4jlpaV4OuYtqHMIB4TShNIQ5JmmCMtPiBLxpl A7VJX8AFP1vbResLT34jPEFip7zHNcepousJZMV4Tn47R7gqHy0HyG0KO8zciDTKVEqu UcljZ0lXICIS4w2rqqfRatFJBQMH0m32PwUiVM7NMc/Z61USWrMY7jF/DrXqP3vzB0Yp Vppqw/pdyJj6lJ894rG1sA4BB8n6e2yNHSfJ74i3u7nljR5xDjoab3i1Q5JfjSMUSDtU m6af23ctvUuwkaRCdp+azfB+H3RQ+DCUuB6Rwd+/cHmqXtvuoDUrLvjwMCK37skAco+T c7uw== X-Gm-Message-State: AOAM531IS/rwggLmA1frs45DSpuDiOU5D8JbqK6CmmFVCi9M9Zg2T3z0 wyagJ6TJZNXZ1EGhoC1m+6w= X-Google-Smtp-Source: ABdhPJyjlaovQTDHEhT2WVdPCM/k+Ja/tl1KYqt8vVw1C7YHSHFiI0u1oimBqywaMKlTeCWDud4gFg== X-Received: by 2002:ac8:248b:: with SMTP id s11mr3307979qts.234.1601657713361; Fri, 02 Oct 2020 09:55:13 -0700 (PDT) Received: from ?IPv6:2620:10d:c0a8:1102:4d4:12a8:4b39:fdd5? ([2620:10d:c091:480::1:e7d]) by smtp.googlemail.com with ESMTPSA id e23sm1399809qkl.67.2020.10.02.09.55.11 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Fri, 02 Oct 2020 09:55:12 -0700 (PDT) To: GCC Patches From: Nathan Sidwell Subject: c++: cleanup ctor_omit_inherited_parms [PR97268] Message-ID: <3e647071-d068-2385-ac41-4e5c0edb2b28@acm.org> Date: Fri, 2 Oct 2020 12:55:11 -0400 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Thunderbird/68.5.0 MIME-Version: 1.0 Content-Language: en-US X-Spam-Status: No, score=-10.0 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_EF, FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM, GIT_PATCH_0, HEADER_FROM_DIFFERENT_DOMAINS, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces@gcc.gnu.org Sender: "Gcc-patches" ctor_omit_inherited_parms was being somewhat abused. What I'd missed is that it checks for a base-dtor name, before proceeding with the check. But we ended up passing it that during cloning before we'd completed the cloning. It was also using DECL_ORIGIN to get to the in-charge ctor, but we sometimes zap DECL_ABSTRACT_ORIGIN, and it ends up processing the incoming function -- which happens to work. so, this breaks out a predicate that expects to get the incharge ctor, and will tell you whether its base ctor will need to omit the parms. We call that directly during cloning. Then the original fn is essentially just a wrapper, but uses DECL_CLONED_FUNCTION to get to the in-charge ctor. That uncovered abuse in add_method, which was happily passing TEMPLATE_DECLs to it. Let's not do that. add_method itself contained a loop mostly containing an 'if (nomatch) continue' idiom, except for a final 'if (match) {...}' check, which itself contained instances of the former idiom. I refactored that to use the former idiom throughout. In doing that I found a place where we'd issue an error, but then not actually reject the new member. gcc/cp/ * cp-tree.h (base_ctor_omit_inherited_parms): Declare. * class. (add_method): Refactor main loop, only pass fns to ctor_omit_inherited_parms. (build_cdtor_clones): Rename bool parms. (clone_cdtor): Call base_ctor_omit_inherited_parms. * method.c (base_ctor_omit_inherited_parms): New, broken out of ... (ctor_omit_inherited_parms): ... here, call it with DECL_CLONED_FUNCTION. gcc/testsuite/ * g++.dg/inherit/pr97268.C: New. pushing to trunk nathan diff --git c/gcc/cp/class.c w/gcc/cp/class.c index c9a1f753d56..01780fe8291 100644 --- c/gcc/cp/class.c +++ w/gcc/cp/class.c @@ -1006,10 +1006,6 @@ add_method (tree type, tree method, bool via_using) for (ovl_iterator iter (current_fns); iter; ++iter) { tree fn = *iter; - tree fn_type; - tree method_type; - tree parms1; - tree parms2; if (TREE_CODE (fn) != TREE_CODE (method)) continue; @@ -1037,10 +1033,8 @@ add_method (tree type, tree method, bool via_using) functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting). */ - fn_type = TREE_TYPE (fn); - method_type = TREE_TYPE (method); - parms1 = TYPE_ARG_TYPES (fn_type); - parms2 = TYPE_ARG_TYPES (method_type); + tree fn_type = TREE_TYPE (fn); + tree method_type = TREE_TYPE (method); /* Compare the quals on the 'this' parm. Don't compare the whole types, as used functions are treated as @@ -1055,137 +1049,149 @@ add_method (tree type, tree method, bool via_using) || type_memfn_rqual (fn_type) != type_memfn_rqual (method_type))) continue; - /* For templates, the return type and template parameters - must be identical. */ - if (TREE_CODE (fn) == TEMPLATE_DECL - && (!same_type_p (TREE_TYPE (fn_type), - TREE_TYPE (method_type)) - || !comp_template_parms (DECL_TEMPLATE_PARMS (fn), - DECL_TEMPLATE_PARMS (method)))) + tree real_fn = fn; + tree real_method = method; + + /* Templates and conversion ops must match return types. */ + if ((DECL_CONV_FN_P (fn) || TREE_CODE (fn) == TEMPLATE_DECL) + && !same_type_p (TREE_TYPE (fn_type), TREE_TYPE (method_type))) continue; + + /* For templates, the template parameters must be identical. */ + if (TREE_CODE (fn) == TEMPLATE_DECL) + { + if (!comp_template_parms (DECL_TEMPLATE_PARMS (fn), + DECL_TEMPLATE_PARMS (method))) + continue; - if (! DECL_STATIC_FUNCTION_P (fn)) + real_fn = DECL_TEMPLATE_RESULT (fn); + real_method = DECL_TEMPLATE_RESULT (method); + } + + tree parms1 = TYPE_ARG_TYPES (fn_type); + tree parms2 = TYPE_ARG_TYPES (method_type); + if (! DECL_STATIC_FUNCTION_P (real_fn)) parms1 = TREE_CHAIN (parms1); - if (! DECL_STATIC_FUNCTION_P (method)) + if (! DECL_STATIC_FUNCTION_P (real_method)) parms2 = TREE_CHAIN (parms2); - /* Bring back parameters omitted from an inherited ctor. */ - if (ctor_omit_inherited_parms (fn)) - parms1 = FUNCTION_FIRST_USER_PARMTYPE (DECL_ORIGIN (fn)); - if (ctor_omit_inherited_parms (method)) - parms2 = FUNCTION_FIRST_USER_PARMTYPE (DECL_ORIGIN (method)); + /* Bring back parameters omitted from an inherited ctor. The + method and the function can have different omittedness. */ + if (ctor_omit_inherited_parms (real_fn)) + parms1 = FUNCTION_FIRST_USER_PARMTYPE (DECL_CLONED_FUNCTION (real_fn)); + if (ctor_omit_inherited_parms (real_method)) + parms2 = (FUNCTION_FIRST_USER_PARMTYPE + (DECL_CLONED_FUNCTION (real_method))); - if (compparms (parms1, parms2) - && (!DECL_CONV_FN_P (fn) - || same_type_p (TREE_TYPE (fn_type), - TREE_TYPE (method_type)))) - { - if (!equivalently_constrained (fn, method)) - { - if (processing_template_decl) - /* We can't check satisfaction in dependent context, wait until - the class is instantiated. */ - continue; - - special_function_kind sfk = special_memfn_p (method); + if (!compparms (parms1, parms2)) + continue; - if (sfk == sfk_none - || DECL_INHERITED_CTOR (fn) - || TREE_CODE (fn) == TEMPLATE_DECL) - /* Member function templates and non-special member functions - coexist if they are not equivalently constrained. A member - function is not hidden by an inherited constructor. */ - continue; + if (!equivalently_constrained (fn, method)) + { + if (processing_template_decl) + /* We can't check satisfaction in dependent context, wait until + the class is instantiated. */ + continue; - /* P0848: For special member functions, deleted, unsatisfied, or - less constrained overloads are ineligible. We implement this - by removing them from CLASSTYPE_MEMBER_VEC. Destructors don't - use the notion of eligibility, and the selected destructor can - be deleted, but removing unsatisfied or less constrained - overloads has the same effect as overload resolution. */ - bool dtor = (sfk == sfk_destructor); - if (losem == -1) - losem = ((!dtor && DECL_DELETED_FN (method)) - || !constraints_satisfied_p (method)); - bool losef = ((!dtor && DECL_DELETED_FN (fn)) - || !constraints_satisfied_p (fn)); - int win; - if (losem || losef) - win = losem - losef; - else - win = more_constrained (fn, method); - if (win > 0) - /* Leave FN in the method vec, discard METHOD. */ - return false; - else if (win < 0) - { - /* Remove FN, add METHOD. */ - current_fns = iter.remove_node (current_fns); - continue; - } - else - /* Let them coexist for now. */ - continue; - } + special_function_kind sfk = special_memfn_p (method); - /* If these are versions of the same function, process and - move on. */ - if (TREE_CODE (fn) == FUNCTION_DECL - && maybe_version_functions (method, fn, true)) + if (sfk == sfk_none + || DECL_INHERITED_CTOR (fn) + || TREE_CODE (fn) == TEMPLATE_DECL) + /* Member function templates and non-special member functions + coexist if they are not equivalently constrained. A member + function is not hidden by an inherited constructor. */ continue; - if (DECL_INHERITED_CTOR (method)) - { - if (DECL_INHERITED_CTOR (fn)) - { - tree basem = DECL_INHERITED_CTOR_BASE (method); - tree basef = DECL_INHERITED_CTOR_BASE (fn); - if (flag_new_inheriting_ctors) - { - if (basem == basef) - { - /* Inheriting the same constructor along different - paths, combine them. */ - SET_DECL_INHERITED_CTOR - (fn, ovl_make (DECL_INHERITED_CTOR (method), - DECL_INHERITED_CTOR (fn))); - /* And discard the new one. */ - return false; - } - else - /* Inherited ctors can coexist until overload - resolution. */ - continue; - } - error_at (DECL_SOURCE_LOCATION (method), - "%q#D conflicts with version inherited from %qT", - method, basef); - inform (DECL_SOURCE_LOCATION (fn), - "version inherited from %qT declared here", - basef); - } - /* Otherwise defer to the other function. */ - return false; - } - - if (via_using) - /* Defer to the local function. */ + /* P0848: For special member functions, deleted, unsatisfied, or + less constrained overloads are ineligible. We implement this + by removing them from CLASSTYPE_MEMBER_VEC. Destructors don't + use the notion of eligibility, and the selected destructor can + be deleted, but removing unsatisfied or less constrained + overloads has the same effect as overload resolution. */ + bool dtor = (sfk == sfk_destructor); + if (losem == -1) + losem = ((!dtor && DECL_DELETED_FN (method)) + || !constraints_satisfied_p (method)); + bool losef = ((!dtor && DECL_DELETED_FN (fn)) + || !constraints_satisfied_p (fn)); + int win; + if (losem || losef) + win = losem - losef; + else + win = more_constrained (fn, method); + if (win > 0) + /* Leave FN in the method vec, discard METHOD. */ return false; - else if (flag_new_inheriting_ctors - && DECL_INHERITED_CTOR (fn)) + else if (win < 0) { - /* Remove the inherited constructor. */ + /* Remove FN, add METHOD. */ current_fns = iter.remove_node (current_fns); continue; } else + /* Let them coexist for now. */ + continue; + } + + /* If these are versions of the same function, process and + move on. */ + if (TREE_CODE (fn) == FUNCTION_DECL + && maybe_version_functions (method, fn, true)) + continue; + + if (DECL_INHERITED_CTOR (method)) + { + if (!DECL_INHERITED_CTOR (fn)) + /* Defer to the other function. */ + return false; + + tree basem = DECL_INHERITED_CTOR_BASE (method); + tree basef = DECL_INHERITED_CTOR_BASE (fn); + if (flag_new_inheriting_ctors) { - error_at (DECL_SOURCE_LOCATION (method), - "%q#D cannot be overloaded with %q#D", method, fn); - inform (DECL_SOURCE_LOCATION (fn), - "previous declaration %q#D", fn); - return false; + if (basem == basef) + { + /* Inheriting the same constructor along different + paths, combine them. */ + SET_DECL_INHERITED_CTOR + (fn, ovl_make (DECL_INHERITED_CTOR (method), + DECL_INHERITED_CTOR (fn))); + /* And discard the new one. */ + return false; + } + else + /* Inherited ctors can coexist until overload + resolution. */ + continue; } + + error_at (DECL_SOURCE_LOCATION (method), + "%q#D conflicts with version inherited from %qT", + method, basef); + inform (DECL_SOURCE_LOCATION (fn), + "version inherited from %qT declared here", + basef); + return false; + } + + if (via_using) + /* Defer to the local function. */ + return false; + else if (flag_new_inheriting_ctors + && DECL_INHERITED_CTOR (fn)) + { + /* Remove the inherited constructor. */ + current_fns = iter.remove_node (current_fns); + continue; + } + else + { + error_at (DECL_SOURCE_LOCATION (method), + "%q#D cannot be overloaded with %q#D", method, fn); + inform (DECL_SOURCE_LOCATION (fn), + "previous declaration %q#D", fn); + return false; } } @@ -4892,7 +4898,7 @@ build_clone (tree fn, tree name, bool need_vtt_parm_p, will be inserted onto DECL_CHAIN of FN. */ static unsigned -build_cdtor_clones (tree fn, bool needs_vtt_parm_p, bool omit_inherited_parms_p) +build_cdtor_clones (tree fn, bool needs_vtt_p, bool base_omits_inherited_p) { unsigned count = 0; @@ -4901,8 +4907,8 @@ build_cdtor_clones (tree fn, bool needs_vtt_parm_p, bool omit_inherited_parms_p) /* For each constructor, we need two variants: an in-charge version and a not-in-charge version. */ build_clone (fn, complete_ctor_identifier, false, false); - build_clone (fn, base_ctor_identifier, needs_vtt_parm_p, - omit_inherited_parms_p); + build_clone (fn, base_ctor_identifier, needs_vtt_p, + base_omits_inherited_p); count += 2; } else @@ -4924,7 +4930,7 @@ build_cdtor_clones (tree fn, bool needs_vtt_parm_p, bool omit_inherited_parms_p) count++; } build_clone (fn, complete_dtor_identifier, false, false); - build_clone (fn, base_dtor_identifier, needs_vtt_parm_p, false); + build_clone (fn, base_dtor_identifier, needs_vtt_p, false); count += 2; } @@ -4948,9 +4954,10 @@ clone_cdtor (tree fn, bool update_methods) /* Base ctor omits inherited parms it needs a vttparm and inherited from a virtual nase ctor. */ - bool omit_inherited = ctor_omit_inherited_parms (fn); + bool base_omits_inherited = (DECL_MAYBE_IN_CHARGE_CONSTRUCTOR_P (fn) + && base_ctor_omit_inherited_parms (fn)); - unsigned count = build_cdtor_clones (fn, vtt, omit_inherited); + unsigned count = build_cdtor_clones (fn, vtt, base_omits_inherited); /* Note that this is an abstract function that is never emitted. */ DECL_ABSTRACT_P (fn) = true; diff --git c/gcc/cp/cp-tree.h w/gcc/cp/cp-tree.h index 9f948aee2d8..43e0c18ec03 100644 --- c/gcc/cp/cp-tree.h +++ w/gcc/cp/cp-tree.h @@ -6764,6 +6764,7 @@ extern tree get_default_ctor (tree); extern tree get_dtor (tree, tsubst_flags_t); extern tree strip_inheriting_ctors (tree); extern tree inherited_ctor_binfo (tree); +extern bool base_ctor_omit_inherited_parms (tree); extern bool ctor_omit_inherited_parms (tree); extern tree locate_ctor (tree); extern tree implicitly_declare_fn (special_function_kind, tree, diff --git c/gcc/cp/method.c w/gcc/cp/method.c index 1058fd05a7d..6e4c5f7e83b 100644 --- c/gcc/cp/method.c +++ w/gcc/cp/method.c @@ -551,31 +551,51 @@ inherited_ctor_binfo (tree fndecl) return inherited_ctor_binfo (binfo, fndecl); } -/* True if we should omit all user-declared parameters from constructor FN, - because it is a base clone of a ctor inherited from a virtual base. */ + +/* True if we should omit all user-declared parameters from a base + construtor built from complete constructor FN. + That's when the ctor is inherited from a virtual base. */ bool -ctor_omit_inherited_parms (tree fn) +base_ctor_omit_inherited_parms (tree comp_ctor) { + gcc_checking_assert (DECL_MAYBE_IN_CHARGE_CONSTRUCTOR_P (comp_ctor)); + if (!flag_new_inheriting_ctors) /* We only optimize away the parameters in the new model. */ return false; - if (!DECL_BASE_CONSTRUCTOR_P (fn) - || !CLASSTYPE_VBASECLASSES (DECL_CONTEXT (fn))) + + if (!CLASSTYPE_VBASECLASSES (DECL_CONTEXT (comp_ctor))) return false; - if (FUNCTION_FIRST_USER_PARMTYPE (DECL_ORIGIN (fn)) == void_list_node) + if (FUNCTION_FIRST_USER_PARMTYPE (comp_ctor) == void_list_node) /* No user-declared parameters to omit. */ return false; - tree binfo = inherited_ctor_binfo (fn); - for (; binfo; binfo = BINFO_INHERITANCE_CHAIN (binfo)) + for (tree binfo = inherited_ctor_binfo (comp_ctor); + binfo; + binfo = BINFO_INHERITANCE_CHAIN (binfo)) if (BINFO_VIRTUAL_P (binfo)) return true; return false; } + +/* True if we should omit all user-declared parameters from constructor FN, + because it is a base clone of a ctor inherited from a virtual base. */ + +bool +ctor_omit_inherited_parms (tree fn) +{ + gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL); + + if (!DECL_BASE_CONSTRUCTOR_P (fn)) + return false; + + return base_ctor_omit_inherited_parms (DECL_CLONED_FUNCTION (fn)); +} + /* True iff constructor(s) INH inherited into BINFO initializes INIT_BINFO. This can be true for multiple virtual bases as well as one direct non-virtual base. */ diff --git c/gcc/testsuite/g++.dg/inherit/pr97268.C w/gcc/testsuite/g++.dg/inherit/pr97268.C new file mode 100644 index 00000000000..79a809cd230 --- /dev/null +++ w/gcc/testsuite/g++.dg/inherit/pr97268.C @@ -0,0 +1,60 @@ +// { dg-do compile { target c++11 } } +// { dg-additional-options -Wall } +// PR 97268, ICE due to broken inherited-from-virtual base-ctor +class Handle { +public: + explicit Handle(char const *const &) { } + ~Handle() {} + Handle(const Handle &) = delete; + Handle &operator=(const Handle &) = delete; + +protected: + int lasterr = 0; + +}; + +struct ObjectBase { + ~ObjectBase() {} + +protected: + explicit ObjectBase(const char *lc_, int ln_, Handle &h, unsigned) + : handle(h) { } + +protected: + + Handle &handle; +}; + +template +struct Object : virtual public ObjectBase { + explicit Object(const char *lc_, int ln_, Handle &env); + +protected: + using ObjectBase::ObjectBase; + +}; + +class BetterObjectBase : virtual public ObjectBase { +protected: + BetterObjectBase(const char *lc_, int ln_, Handle &env) + : ObjectBase("", 0, env, 0) {} + +}; + +template +class BetterObject : public Object, public BetterObjectBase { +public: + BetterObject(Handle &env) + : ObjectBase("", 0, env, 0) + , Object("", 0, env, 0) + , BetterObjectBase("", 0, env) {} // { dg-error "use of deleted function" } + +}; + +int main() { + Handle h("handle"); + + BetterObject B(h); + + return 0; +}