mirror of
https://gcc.gnu.org/git/gcc.git
synced 2026-02-22 20:01:22 -05:00
c++: Implement C++20 'using enum'. [PR91367]
This feature allows the programmer to import enumerator names into the current scope so later mentions don't need to use the fully-qualified name. These usings are not subject to the usual restrictions on using-decls: in particular, they can move between class and non-class scopes, and between classes that are not related by inheritance. This last caused difficulty for our normal approach to using-decls within a class hierarchy, as we assume that the class where we looked up a used declaration is derived from the class where it was first declared. So to simplify things, in that case we make a clone of the CONST_DECL in the using class. Thanks to Nathan for the start of this work: in particular, the lookup_using_decl rewrite. The changes to dwarf2out revealed an existing issue with the D front-end: we were doing the wrong thing for importing a D CONST_DECL, because dwarf2out_imported_module_or_decl_1 was looking through it to its type, expecting it to be an enumerator, but in one case in thread.d, the constant had type int. Adding the ability to import a C++ enumerator also fixed that, but that led to a crash in force_decl_die, which didn't know what to do with a CONST_DECL. So now it does. Co-authored-by: Nathan Sidwell <nathan@acm.org> gcc/cp/ChangeLog: * cp-tree.h (USING_DECL_UNRELATED_P): New. (CONST_DECL_USING_P): New. * class.c (handle_using_decl): If USING_DECL_UNRELATED_P, clone the CONST_DECL. * name-lookup.c (supplement_binding_1): A clone hides its using-declaration. (lookup_using_decl): Rewrite to separate lookup and validation. (do_class_using_decl): Adjust. (finish_nonmember_using_decl): Adjust. * parser.c (make_location): Add cp_token overload. (finish_using_decl): Split out from... (cp_parser_using_declaration): ...here. Don't look through enums. (cp_parser_using_enum): New. (cp_parser_block_declaration): Call it. (cp_parser_member_declaration): Call it. * semantics.c (finish_id_expression_1): Handle enumerator used from class scope. gcc/ChangeLog: * dwarf2out.c (gen_enumeration_type_die): Call equate_decl_number_to_die for enumerators. (gen_member_die): Don't move enumerators to their enclosing class. (dwarf2out_imported_module_or_decl_1): Allow importing individual enumerators. (force_decl_die): Handle CONST_DECL. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/inh-ctor28.C: Adjust expected diagnostic. * g++.dg/cpp0x/inh-ctor33.C: Likewise. * g++.dg/cpp0x/using-enum-1.C: Add comment. * g++.dg/cpp0x/using-enum-2.C: Allowed in C++20. * g++.dg/cpp0x/using-enum-3.C: Likewise. * g++.dg/cpp1z/class-deduction69.C: Adjust diagnostic. * g++.dg/inherit/using5.C: Likewise. * g++.dg/cpp2a/using-enum-1.C: New test. * g++.dg/cpp2a/using-enum-2.C: New test. * g++.dg/cpp2a/using-enum-3.C: New test. * g++.dg/cpp2a/using-enum-4.C: New test. * g++.dg/cpp2a/using-enum-5.C: New test. * g++.dg/cpp2a/using-enum-6.C: New test. * g++.dg/debug/dwarf2/using-enum.C: New test.
This commit is contained in:
@@ -1331,6 +1331,23 @@ handle_using_decl (tree using_decl, tree t)
|
||||
add_method (t, *iter, true);
|
||||
alter_access (t, *iter, access);
|
||||
}
|
||||
else if (USING_DECL_UNRELATED_P (using_decl))
|
||||
{
|
||||
/* C++20 using enum can import non-inherited enumerators into class
|
||||
scope. We implement that by making a copy of the CONST_DECL for which
|
||||
CONST_DECL_USING_P is true. */
|
||||
gcc_assert (TREE_CODE (decl) == CONST_DECL);
|
||||
|
||||
tree copy = copy_decl (decl);
|
||||
DECL_CONTEXT (copy) = t;
|
||||
DECL_ARTIFICIAL (copy) = true;
|
||||
/* We emitted debug info for the USING_DECL above; make sure we don't
|
||||
also emit anything for this clone. */
|
||||
DECL_IGNORED_P (copy) = true;
|
||||
DECL_SOURCE_LOCATION (copy) = DECL_SOURCE_LOCATION (using_decl);
|
||||
finish_member_declaration (copy);
|
||||
DECL_ABSTRACT_ORIGIN (copy) = decl;
|
||||
}
|
||||
else
|
||||
alter_access (t, decl, access);
|
||||
}
|
||||
|
||||
@@ -529,6 +529,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
|
||||
TEMPLATE_DECL_COMPLEX_ALIAS_P (in TEMPLATE_DECL)
|
||||
DECL_INSTANTIATING_NSDMI_P (in a FIELD_DECL)
|
||||
LABEL_DECL_CDTOR (in LABEL_DECL)
|
||||
USING_DECL_UNRELATED_P (in USING_DECL)
|
||||
3: DECL_IN_AGGR_P.
|
||||
4: DECL_C_BIT_FIELD (in a FIELD_DECL)
|
||||
DECL_ANON_UNION_VAR_P (in a VAR_DECL)
|
||||
@@ -3409,6 +3410,16 @@ struct GTY(()) lang_decl {
|
||||
/* Non zero if the using decl refers to a dependent type. */
|
||||
#define USING_DECL_TYPENAME_P(NODE) DECL_LANG_FLAG_1 (USING_DECL_CHECK (NODE))
|
||||
|
||||
/* True if member using decl NODE refers to a non-inherited NODE. */
|
||||
#define USING_DECL_UNRELATED_P(NODE) DECL_LANG_FLAG_2 (USING_DECL_CHECK (NODE))
|
||||
|
||||
/* True iff the CONST_DECL is a class-scope clone from C++20 using enum,
|
||||
created by handle_using_decl. */
|
||||
#define CONST_DECL_USING_P(NODE) \
|
||||
(TREE_CODE (NODE) == CONST_DECL \
|
||||
&& TREE_CODE (TREE_TYPE (NODE)) == ENUMERAL_TYPE \
|
||||
&& DECL_CONTEXT (NODE) != TREE_TYPE (NODE))
|
||||
|
||||
/* In a FUNCTION_DECL, this is nonzero if this function was defined in
|
||||
the class definition. We have saved away the text of the function,
|
||||
but have not yet processed it. */
|
||||
|
||||
@@ -2125,6 +2125,10 @@ supplement_binding_1 (cxx_binding *binding, tree decl)
|
||||
region to refer only to the namespace to which it already
|
||||
refers. */
|
||||
ok = false;
|
||||
else if (TREE_CODE (bval) == USING_DECL
|
||||
&& CONST_DECL_USING_P (decl))
|
||||
/* Let the clone hide the using-decl that introduced it. */
|
||||
binding->value = decl;
|
||||
else
|
||||
{
|
||||
if (!error_operand_p (bval))
|
||||
@@ -4540,43 +4544,66 @@ push_class_level_binding (tree name, tree x)
|
||||
/* Process and lookup a using decl SCOPE::lookup.name, filling in
|
||||
lookup.values & lookup.type. Return true if ok. */
|
||||
|
||||
static bool
|
||||
static tree
|
||||
lookup_using_decl (tree scope, name_lookup &lookup)
|
||||
{
|
||||
tree current = current_scope ();
|
||||
bool dependent_p = false;
|
||||
tree binfo = NULL_TREE;
|
||||
base_kind b_kind = bk_not_base;
|
||||
|
||||
/* Because C++20 breaks the invariant that only member using-decls
|
||||
refer to members and only non-member using-decls refer to
|
||||
non-members, we first do the lookups, and then do validation that
|
||||
what we found is ok. */
|
||||
|
||||
if (TREE_CODE (scope) == ENUMERAL_TYPE
|
||||
&& cxx_dialect < cxx20
|
||||
&& UNSCOPED_ENUM_P (scope)
|
||||
&& !TYPE_FUNCTION_SCOPE_P (scope))
|
||||
{
|
||||
/* PR c++/60265 argued that since C++11 added explicit enum scope, we
|
||||
should allow it as meaning the enclosing scope. I don't see any
|
||||
justification for this in C++11, but let's keep allowing it. */
|
||||
tree ctx = CP_TYPE_CONTEXT (scope);
|
||||
if (CLASS_TYPE_P (ctx) == CLASS_TYPE_P (current))
|
||||
scope = ctx;
|
||||
}
|
||||
|
||||
if (TREE_CODE (scope) == NAMESPACE_DECL)
|
||||
{
|
||||
/* Naming a namespace member. */
|
||||
if (TYPE_P (current))
|
||||
qualified_namespace_lookup (scope, &lookup);
|
||||
|
||||
if (TYPE_P (current)
|
||||
&& (!lookup.value
|
||||
|| lookup.type
|
||||
|| cxx_dialect < cxx20
|
||||
|| TREE_CODE (lookup.value) != CONST_DECL))
|
||||
{
|
||||
error ("using-declaration for non-member at class scope");
|
||||
return false;
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
qualified_namespace_lookup (scope, &lookup);
|
||||
}
|
||||
else if (TREE_CODE (scope) == ENUMERAL_TYPE)
|
||||
{
|
||||
error ("using-declaration may not name enumerator %<%E::%D%>",
|
||||
scope, lookup.name);
|
||||
return false;
|
||||
/* Naming an enumeration member. */
|
||||
if (cxx_dialect < cxx20)
|
||||
error ("%<using%> with enumeration scope %q#T "
|
||||
"only available with %<-std=c++20%> or %<-std=gnu++20%>",
|
||||
scope);
|
||||
lookup.value = lookup_enumerator (scope, lookup.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Naming a class member. */
|
||||
if (!TYPE_P (current))
|
||||
{
|
||||
error ("using-declaration for member at non-class scope");
|
||||
return false;
|
||||
}
|
||||
/* Naming a class member. This is awkward in C++20, because we
|
||||
might be naming an enumerator of an unrelated class. */
|
||||
|
||||
/* Make sure the name is not invalid */
|
||||
/* You cannot using-decl a destructor. */
|
||||
if (TREE_CODE (lookup.name) == BIT_NOT_EXPR)
|
||||
{
|
||||
error ("%<%T::%D%> names destructor", scope, lookup.name);
|
||||
return false;
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
/* Using T::T declares inheriting ctors, even if T is a typedef. */
|
||||
@@ -4584,91 +4611,150 @@ lookup_using_decl (tree scope, name_lookup &lookup)
|
||||
&& (lookup.name == TYPE_IDENTIFIER (scope)
|
||||
|| constructor_name_p (lookup.name, scope)))
|
||||
{
|
||||
if (!TYPE_P (current))
|
||||
{
|
||||
error ("non-member using-decl names constructor of %qT", scope);
|
||||
return NULL_TREE;
|
||||
}
|
||||
maybe_warn_cpp0x (CPP0X_INHERITING_CTORS);
|
||||
lookup.name = ctor_identifier;
|
||||
CLASSTYPE_NON_AGGREGATE (current) = true;
|
||||
}
|
||||
|
||||
if (!MAYBE_CLASS_TYPE_P (scope))
|
||||
;
|
||||
else if (TYPE_P (current))
|
||||
{
|
||||
dependent_p = dependent_scope_p (scope);
|
||||
if (!dependent_p)
|
||||
{
|
||||
binfo = lookup_base (current, scope, ba_any, &b_kind, tf_none);
|
||||
gcc_checking_assert (b_kind >= bk_not_base);
|
||||
|
||||
if (lookup.name == ctor_identifier)
|
||||
{
|
||||
/* Even if there are dependent bases, SCOPE will not
|
||||
be direct base, no matter. */
|
||||
if (b_kind < bk_proper_base || !binfo_direct_p (binfo))
|
||||
{
|
||||
error ("%qT is not a direct base of %qT", scope, current);
|
||||
return NULL_TREE;
|
||||
}
|
||||
}
|
||||
else if (b_kind < bk_proper_base)
|
||||
binfo = TYPE_BINFO (scope);
|
||||
else if (IDENTIFIER_CONV_OP_P (lookup.name)
|
||||
&& dependent_type_p (TREE_TYPE (lookup.name)))
|
||||
dependent_p = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
binfo = TYPE_BINFO (scope);
|
||||
|
||||
if (!dependent_p)
|
||||
{
|
||||
if (binfo)
|
||||
lookup.value = lookup_member (binfo, lookup.name, /*protect=*/2,
|
||||
/*want_type=*/false, tf_none);
|
||||
|
||||
tree saved_value = lookup.value;
|
||||
if (lookup.value
|
||||
&& b_kind < bk_proper_base)
|
||||
{
|
||||
if (cxx_dialect >= cxx20
|
||||
&& TREE_CODE (lookup.value) == CONST_DECL)
|
||||
{
|
||||
/* Using an unrelated enum; check access here rather
|
||||
than separately for class and non-class using. */
|
||||
perform_or_defer_access_check
|
||||
(binfo, lookup.value, lookup.value, tf_warning_or_error);
|
||||
/* And then if this is a copy from handle_using_decl, look
|
||||
through to the original enumerator. */
|
||||
if (CONST_DECL_USING_P (lookup.value))
|
||||
lookup.value = DECL_ABSTRACT_ORIGIN (lookup.value);
|
||||
}
|
||||
else
|
||||
lookup.value = NULL_TREE;
|
||||
}
|
||||
|
||||
if (!lookup.value)
|
||||
{
|
||||
if (!TYPE_P (current))
|
||||
{
|
||||
error ("using-declaration for member at non-class scope");
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
if (b_kind < bk_proper_base)
|
||||
{
|
||||
if (b_kind == bk_not_base && any_dependent_bases_p ())
|
||||
/* Treat as-if dependent. */
|
||||
dependent_p = true;
|
||||
else
|
||||
{
|
||||
auto_diagnostic_group g;
|
||||
error_not_base_type (scope, current);
|
||||
if (saved_value && DECL_IMPLICIT_TYPEDEF_P (saved_value)
|
||||
&& (TREE_CODE (TREE_TYPE (saved_value))
|
||||
== ENUMERAL_TYPE))
|
||||
inform (input_location,
|
||||
"did you mean %<using enum %T::%D%>?",
|
||||
scope, lookup.name);
|
||||
return NULL_TREE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Did we find anything sane? */
|
||||
if (dependent_p)
|
||||
;
|
||||
else if (!lookup.value)
|
||||
{
|
||||
error ("%qD has not been declared in %qD", lookup.name, scope);
|
||||
return NULL_TREE;
|
||||
}
|
||||
else if (TREE_CODE (lookup.value) == TREE_LIST
|
||||
/* We can (independently) have ambiguous implicit typedefs. */
|
||||
|| (lookup.type && TREE_CODE (lookup.type) == TREE_LIST))
|
||||
{
|
||||
error ("reference to %qD is ambiguous", lookup.name);
|
||||
print_candidates (TREE_CODE (lookup.value) == TREE_LIST
|
||||
? lookup.value : lookup.type);
|
||||
return NULL_TREE;
|
||||
}
|
||||
else if (TREE_CODE (lookup.value) == NAMESPACE_DECL)
|
||||
{
|
||||
error ("using-declaration may not name namespace %qD", lookup.value);
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
if (TYPE_P (current))
|
||||
{
|
||||
/* In class scope. */
|
||||
|
||||
/* Cannot introduce a constructor name. */
|
||||
if (constructor_name_p (lookup.name, current))
|
||||
{
|
||||
error ("%<%T::%D%> names constructor in %qT",
|
||||
scope, lookup.name, current);
|
||||
return false;
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
/* Member using decls finish processing when completing the
|
||||
class. */
|
||||
/* From [namespace.udecl]:
|
||||
|
||||
A using-declaration used as a member-declaration shall refer
|
||||
to a member of a base class of the class being defined.
|
||||
|
||||
In general, we cannot check this constraint in a template
|
||||
because we do not know the entire set of base classes of the
|
||||
current class type. Morover, if SCOPE is dependent, it might
|
||||
match a non-dependent base. */
|
||||
|
||||
dependent_p = dependent_scope_p (scope);
|
||||
if (!dependent_p)
|
||||
{
|
||||
base_kind b_kind;
|
||||
tree binfo = lookup_base (current, scope, ba_any, &b_kind,
|
||||
tf_warning_or_error);
|
||||
if (b_kind < bk_proper_base)
|
||||
{
|
||||
/* If there are dependent bases, scope might resolve at
|
||||
instantiation time, even if it isn't exactly one of
|
||||
the dependent bases. */
|
||||
if (b_kind == bk_same_type || !any_dependent_bases_p ())
|
||||
{
|
||||
error_not_base_type (scope, current);
|
||||
return false;
|
||||
}
|
||||
/* Treat as-if dependent. */
|
||||
dependent_p = true;
|
||||
}
|
||||
else if (lookup.name == ctor_identifier && !binfo_direct_p (binfo))
|
||||
{
|
||||
error ("cannot inherit constructors from indirect base %qT",
|
||||
scope);
|
||||
return false;
|
||||
}
|
||||
else if (IDENTIFIER_CONV_OP_P (lookup.name)
|
||||
&& dependent_type_p (TREE_TYPE (lookup.name)))
|
||||
dependent_p = true;
|
||||
else
|
||||
lookup.value = lookup_member (binfo, lookup.name, 0,
|
||||
false, tf_warning_or_error);
|
||||
}
|
||||
if (lookup.value && BASELINK_P (lookup.value))
|
||||
/* The binfo from which the functions came does not matter. */
|
||||
lookup.value = BASELINK_FUNCTIONS (lookup.value);
|
||||
}
|
||||
|
||||
if (!dependent_p)
|
||||
{
|
||||
if (!lookup.value)
|
||||
{
|
||||
error ("%qD has not been declared in %qE", lookup.name, scope);
|
||||
return false;
|
||||
}
|
||||
tree using_decl = build_lang_decl (USING_DECL, lookup.name, NULL_TREE);
|
||||
USING_DECL_SCOPE (using_decl) = scope;
|
||||
USING_DECL_DECLS (using_decl) = lookup.value;
|
||||
DECL_DEPENDENT_P (using_decl) = dependent_p;
|
||||
if (TYPE_P (current) && b_kind == bk_not_base)
|
||||
USING_DECL_UNRELATED_P (using_decl) = true;
|
||||
|
||||
if (TREE_CODE (lookup.value) == TREE_LIST
|
||||
/* We can (independently) have ambiguous implicit typedefs. */
|
||||
|| (lookup.type && TREE_CODE (lookup.type) == TREE_LIST))
|
||||
{
|
||||
error ("reference to %qD is ambiguous", lookup.name);
|
||||
print_candidates (TREE_CODE (lookup.value) == TREE_LIST
|
||||
? lookup.value : lookup.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TREE_CODE (lookup.value) == NAMESPACE_DECL)
|
||||
{
|
||||
error ("using-declaration may not name namespace %qD", lookup.value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return using_decl;
|
||||
}
|
||||
|
||||
/* Process "using SCOPE::NAME" in a class scope. Return the
|
||||
@@ -4682,20 +4768,7 @@ do_class_using_decl (tree scope, tree name)
|
||||
return NULL_TREE;
|
||||
|
||||
name_lookup lookup (name);
|
||||
if (!lookup_using_decl (scope, lookup))
|
||||
return NULL_TREE;
|
||||
|
||||
tree found = lookup.value;
|
||||
if (found && BASELINK_P (found))
|
||||
/* The binfo from which the functions came does not matter. */
|
||||
found = BASELINK_FUNCTIONS (found);
|
||||
|
||||
tree using_decl = build_lang_decl (USING_DECL, lookup.name, NULL_TREE);
|
||||
USING_DECL_SCOPE (using_decl) = scope;
|
||||
USING_DECL_DECLS (using_decl) = found;
|
||||
DECL_DEPENDENT_P (using_decl) = !found;
|
||||
|
||||
return using_decl;
|
||||
return lookup_using_decl (scope, lookup);
|
||||
}
|
||||
|
||||
|
||||
@@ -5076,7 +5149,8 @@ finish_nonmember_using_decl (tree scope, tree name)
|
||||
|
||||
name_lookup lookup (name);
|
||||
|
||||
if (!lookup_using_decl (scope, lookup))
|
||||
tree using_decl = lookup_using_decl (scope, lookup);
|
||||
if (!using_decl)
|
||||
return;
|
||||
|
||||
/* Emit debug info. */
|
||||
@@ -5105,8 +5179,6 @@ finish_nonmember_using_decl (tree scope, tree name)
|
||||
}
|
||||
else
|
||||
{
|
||||
tree using_decl = build_lang_decl (USING_DECL, lookup.name, NULL_TREE);
|
||||
USING_DECL_SCOPE (using_decl) = scope;
|
||||
add_decl_expr (using_decl);
|
||||
|
||||
cxx_binding *binding = find_local_binding (current_binding_level, name);
|
||||
|
||||
145
gcc/cp/parser.c
145
gcc/cp/parser.c
@@ -808,6 +808,14 @@ make_location (location_t caret, location_t start, cp_lexer *lexer)
|
||||
return make_location (caret, start, t->location);
|
||||
}
|
||||
|
||||
/* Overload for make_location taking tokens instead of locations. */
|
||||
|
||||
static inline location_t
|
||||
make_location (cp_token *caret, cp_token *start, cp_token *end)
|
||||
{
|
||||
return make_location (caret->location, start->location, end->location);
|
||||
}
|
||||
|
||||
/* nonzero if we are presently saving tokens. */
|
||||
|
||||
static inline int
|
||||
@@ -2233,6 +2241,8 @@ static bool cp_parser_using_declaration
|
||||
(cp_parser *, bool);
|
||||
static void cp_parser_using_directive
|
||||
(cp_parser *);
|
||||
static void cp_parser_using_enum
|
||||
(cp_parser *);
|
||||
static tree cp_parser_alias_declaration
|
||||
(cp_parser *);
|
||||
static void cp_parser_asm_definition
|
||||
@@ -13726,6 +13736,8 @@ cp_parser_block_declaration (cp_parser *parser,
|
||||
token2 = cp_lexer_peek_nth_token (parser->lexer, 2);
|
||||
if (token2->keyword == RID_NAMESPACE)
|
||||
cp_parser_using_directive (parser);
|
||||
else if (token2->keyword == RID_ENUM)
|
||||
cp_parser_using_enum (parser);
|
||||
/* If the second token after 'using' is '=', then we have an
|
||||
alias-declaration. */
|
||||
else if (cxx_dialect >= cxx11
|
||||
@@ -20010,6 +20022,31 @@ cp_parser_qualified_namespace_specifier (cp_parser* parser)
|
||||
return cp_parser_namespace_name (parser);
|
||||
}
|
||||
|
||||
/* Subroutine of cp_parser_using_declaration. */
|
||||
|
||||
static tree
|
||||
finish_using_decl (tree qscope, tree identifier, bool typename_p = false)
|
||||
{
|
||||
tree decl = NULL_TREE;
|
||||
if (at_class_scope_p ())
|
||||
{
|
||||
/* Create the USING_DECL. */
|
||||
decl = do_class_using_decl (qscope, identifier);
|
||||
|
||||
if (check_for_bare_parameter_packs (decl))
|
||||
return error_mark_node;
|
||||
|
||||
if (decl && typename_p)
|
||||
USING_DECL_TYPENAME_P (decl) = 1;
|
||||
|
||||
/* Add it to the list of members in this class. */
|
||||
finish_member_declaration (decl);
|
||||
}
|
||||
else
|
||||
finish_nonmember_using_decl (qscope, identifier);
|
||||
return decl;
|
||||
}
|
||||
|
||||
/* Parse a using-declaration, or, if ACCESS_DECLARATION_P is true, an
|
||||
access declaration.
|
||||
|
||||
@@ -20029,7 +20066,6 @@ cp_parser_using_declaration (cp_parser* parser,
|
||||
cp_token *token;
|
||||
bool typename_p = false;
|
||||
bool global_scope_p;
|
||||
tree decl;
|
||||
tree identifier;
|
||||
tree qscope;
|
||||
int oldcount = errorcount;
|
||||
@@ -20088,9 +20124,6 @@ cp_parser_using_declaration (cp_parser* parser,
|
||||
/*is_declaration=*/true);
|
||||
if (!qscope)
|
||||
qscope = global_namespace;
|
||||
else if (UNSCOPED_ENUM_P (qscope)
|
||||
&& !TYPE_FUNCTION_SCOPE_P (qscope))
|
||||
qscope = CP_TYPE_CONTEXT (qscope);
|
||||
|
||||
cp_warn_deprecated_use_scopes (qscope);
|
||||
|
||||
@@ -20138,25 +20171,13 @@ cp_parser_using_declaration (cp_parser* parser,
|
||||
"a template-id may not appear in a using-declaration");
|
||||
else
|
||||
{
|
||||
if (at_class_scope_p ())
|
||||
tree decl = finish_using_decl (qscope, identifier, typename_p);
|
||||
|
||||
if (decl == error_mark_node)
|
||||
{
|
||||
/* Create the USING_DECL. */
|
||||
decl = do_class_using_decl (qscope, identifier);
|
||||
|
||||
if (decl && typename_p)
|
||||
USING_DECL_TYPENAME_P (decl) = 1;
|
||||
|
||||
if (check_for_bare_parameter_packs (decl))
|
||||
{
|
||||
cp_parser_require (parser, CPP_SEMICOLON, RT_SEMICOLON);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
/* Add it to the list of members in this class. */
|
||||
finish_member_declaration (decl);
|
||||
cp_parser_require (parser, CPP_SEMICOLON, RT_SEMICOLON);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
finish_nonmember_using_decl (qscope, identifier);
|
||||
}
|
||||
|
||||
if (!access_declaration_p
|
||||
@@ -20182,6 +20203,76 @@ cp_parser_using_declaration (cp_parser* parser,
|
||||
return true;
|
||||
}
|
||||
|
||||
/* C++20 using enum declaration.
|
||||
|
||||
using-enum-declaration :
|
||||
using elaborated-enum-specifier ; */
|
||||
|
||||
static void
|
||||
cp_parser_using_enum (cp_parser *parser)
|
||||
{
|
||||
cp_parser_require_keyword (parser, RID_USING, RT_USING);
|
||||
|
||||
/* Using cp_parser_elaborated_type_specifier rejects typedef-names, which
|
||||
breaks one of the motivating examples in using-enum-5.C.
|
||||
cp_parser_simple_type_specifier seems to be closer to what we actually
|
||||
want, though that hasn't been properly specified yet. */
|
||||
|
||||
/* Consume 'enum'. */
|
||||
gcc_checking_assert (cp_lexer_next_token_is_keyword (parser->lexer, RID_ENUM));
|
||||
cp_lexer_consume_token (parser->lexer);
|
||||
|
||||
cp_token *start = cp_lexer_peek_token (parser->lexer);
|
||||
|
||||
tree type = (cp_parser_simple_type_specifier
|
||||
(parser, NULL, CP_PARSER_FLAGS_TYPENAME_OPTIONAL));
|
||||
|
||||
cp_token *end = cp_lexer_previous_token (parser->lexer);
|
||||
|
||||
if (type == error_mark_node
|
||||
|| !cp_parser_require (parser, CPP_SEMICOLON, RT_SEMICOLON))
|
||||
{
|
||||
cp_parser_skip_to_end_of_block_or_statement (parser);
|
||||
return;
|
||||
}
|
||||
if (TREE_CODE (type) == TYPE_DECL)
|
||||
type = TREE_TYPE (type);
|
||||
|
||||
/* The elaborated-enum-specifier shall not name a dependent type and the type
|
||||
shall have a reachable enum-specifier. */
|
||||
const char *msg = nullptr;
|
||||
if (cxx_dialect < cxx20)
|
||||
msg = _("%<using enum%> "
|
||||
"only available with %<-std=c++20%> or %<-std=gnu++20%>");
|
||||
else if (dependent_type_p (type))
|
||||
msg = _("%<using enum%> of dependent type %qT");
|
||||
else if (TREE_CODE (type) != ENUMERAL_TYPE)
|
||||
msg = _("%<using enum%> of non-enumeration type %q#T");
|
||||
else if (!COMPLETE_TYPE_P (type))
|
||||
msg = _("%<using enum%> of incomplete type %qT");
|
||||
else if (OPAQUE_ENUM_P (type))
|
||||
msg = _("%<using enum%> of %qT before its enum-specifier");
|
||||
if (msg)
|
||||
{
|
||||
location_t loc = make_location (start, start, end);
|
||||
auto_diagnostic_group g;
|
||||
error_at (loc, msg, type);
|
||||
loc = location_of (type);
|
||||
if (cxx_dialect < cxx20 || loc == input_location)
|
||||
;
|
||||
else if (OPAQUE_ENUM_P (type))
|
||||
inform (loc, "opaque-enum-declaration here");
|
||||
else
|
||||
inform (loc, "declared here");
|
||||
}
|
||||
|
||||
/* A using-enum-declaration introduces the enumerator names of the named
|
||||
enumeration as if by a using-declaration for each enumerator. */
|
||||
if (TREE_CODE (type) == ENUMERAL_TYPE)
|
||||
for (tree v = TYPE_VALUES (type); v; v = TREE_CHAIN (v))
|
||||
finish_using_decl (type, DECL_NAME (TREE_VALUE (v)));
|
||||
}
|
||||
|
||||
/* Parse an alias-declaration.
|
||||
|
||||
alias-declaration:
|
||||
@@ -25279,12 +25370,10 @@ cp_parser_member_declaration (cp_parser* parser)
|
||||
if (cp_lexer_next_token_is_keyword (parser->lexer, RID_USING))
|
||||
{
|
||||
if (cxx_dialect < cxx11)
|
||||
{
|
||||
/* Parse the using-declaration. */
|
||||
cp_parser_using_declaration (parser,
|
||||
/*access_declaration_p=*/false);
|
||||
return;
|
||||
}
|
||||
/* Parse the using-declaration. */
|
||||
cp_parser_using_declaration (parser, /*access_declaration_p=*/false);
|
||||
else if (cp_lexer_nth_token_is_keyword (parser->lexer, 2, RID_ENUM))
|
||||
cp_parser_using_enum (parser);
|
||||
else
|
||||
{
|
||||
tree decl;
|
||||
@@ -25305,8 +25394,8 @@ cp_parser_member_declaration (cp_parser* parser)
|
||||
else
|
||||
cp_parser_using_declaration (parser,
|
||||
/*access_declaration_p=*/false);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check for @defs. */
|
||||
|
||||
@@ -4019,9 +4019,17 @@ finish_id_expression_1 (tree id_expression,
|
||||
if (context != current_class_type)
|
||||
{
|
||||
tree path = currently_open_derived_class (context);
|
||||
perform_or_defer_access_check (TYPE_BINFO (path),
|
||||
decl, decl,
|
||||
tf_warning_or_error);
|
||||
if (!path)
|
||||
/* PATH can be null for using an enum of an unrelated
|
||||
class; we checked its access in lookup_using_decl.
|
||||
|
||||
??? Should this case make a clone instead, like
|
||||
handle_using_decl? */
|
||||
gcc_assert (TREE_CODE (decl) == CONST_DECL);
|
||||
else
|
||||
perform_or_defer_access_check (TYPE_BINFO (path),
|
||||
decl, decl,
|
||||
tf_warning_or_error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22193,6 +22193,9 @@ gen_enumeration_type_die (tree type, dw_die_ref context_die)
|
||||
dw_die_ref enum_die = new_die (DW_TAG_enumerator, type_die, link);
|
||||
tree value = TREE_VALUE (link);
|
||||
|
||||
if (DECL_P (value))
|
||||
equate_decl_number_to_die (value, enum_die);
|
||||
|
||||
gcc_assert (!ENUM_IS_OPAQUE (type));
|
||||
add_name_attribute (enum_die,
|
||||
IDENTIFIER_POINTER (TREE_PURPOSE (link)));
|
||||
@@ -25247,6 +25250,10 @@ gen_member_die (tree type, dw_die_ref context_die)
|
||||
splice = false;
|
||||
}
|
||||
}
|
||||
else if (child->die_tag == DW_TAG_enumerator)
|
||||
/* Enumerators remain under their enumeration even if
|
||||
their names are introduced in the enclosing scope. */
|
||||
splice = false;
|
||||
|
||||
if (splice)
|
||||
splice_child_die (context_die, child);
|
||||
@@ -26158,6 +26165,13 @@ force_decl_die (tree decl)
|
||||
decl_die = comp_unit_die ();
|
||||
break;
|
||||
|
||||
case CONST_DECL:
|
||||
/* Enumerators shouldn't need force_decl_die. */
|
||||
gcc_assert (DECL_CONTEXT (decl) == NULL_TREE
|
||||
|| TREE_CODE (DECL_CONTEXT (decl)) != ENUMERAL_TYPE);
|
||||
gen_decl_die (decl, NULL, NULL, context_die);
|
||||
break;
|
||||
|
||||
case TRANSLATION_UNIT_DECL:
|
||||
decl_die = comp_unit_die ();
|
||||
break;
|
||||
@@ -26743,7 +26757,7 @@ dwarf2out_imported_module_or_decl_1 (tree decl,
|
||||
else
|
||||
xloc = expand_location (input_location);
|
||||
|
||||
if (TREE_CODE (decl) == TYPE_DECL || TREE_CODE (decl) == CONST_DECL)
|
||||
if (TREE_CODE (decl) == TYPE_DECL)
|
||||
{
|
||||
at_import_die = force_type_die (TREE_TYPE (decl));
|
||||
/* For namespace N { typedef void T; } using N::T; base_type_die
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
struct A {};
|
||||
struct B : virtual A {};
|
||||
struct C : virtual A {};
|
||||
struct D : B,C { using A::A; }; // { dg-error "indirect" }
|
||||
struct D : B,C { using A::A; }; // { dg-error "not a direct base" }
|
||||
|
||||
@@ -10,7 +10,7 @@ public:
|
||||
class Y : public X { };
|
||||
|
||||
class Z : public Y {
|
||||
using X::X; // { dg-error "cannot inherit constructors from indirect base .X." }
|
||||
using X::X; // { dg-error ".X. is not a direct base of .Z." }
|
||||
};
|
||||
|
||||
int main()
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// PR c++/60265
|
||||
// { dg-do compile { target c++11 } }
|
||||
|
||||
// [namespace.udecl]/7 shall not name a scoped enumerator.
|
||||
// (so unscoped enumerator is ok)
|
||||
|
||||
namespace A
|
||||
{
|
||||
enum E { V };
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
// PR c++/60265
|
||||
// { dg-do compile { target c++11 } }
|
||||
|
||||
// [namespace.udecl]/7 shall not name a scoped enumerator.
|
||||
// (this changes in C++2a)
|
||||
|
||||
namespace A
|
||||
{
|
||||
enum class E { V };
|
||||
|
||||
using E::V; // { dg-error "name enumerator" }
|
||||
using E::V; // { dg-error "enum" "" { target { ! c++2a } } }
|
||||
}
|
||||
|
||||
void foo()
|
||||
{
|
||||
using A::E::V; // { dg-error "name enumerator" }
|
||||
using A::E::V; // { dg-error "enum" "" { target { ! c++2a } } }
|
||||
}
|
||||
|
||||
using A::E::V; // { dg-error "name enumerator" }
|
||||
using A::E::V; // { dg-error "enum" "" { target { ! c++2a } } }
|
||||
|
||||
enum class F { U };
|
||||
|
||||
using F::U; // { dg-error "name enumerator" }
|
||||
using F::U; // { dg-error "enum" "" { target { ! c++2a } } }
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
// PR c++/89511
|
||||
// { dg-do compile { target c++11 } }
|
||||
|
||||
// [namespace.udecl] In a using-declaration used as a
|
||||
// member-declaration, the nested-name-specifier shall name a base
|
||||
// class of the class being defined
|
||||
// (this changes in C++2a)
|
||||
|
||||
void f ()
|
||||
{
|
||||
enum e { a };
|
||||
using e::a; // { dg-error "name enumerator" }
|
||||
using e::a; // { dg-error "redeclaration" }
|
||||
// { dg-error "enum" "" { target { ! c++2a } } .-1 }
|
||||
}
|
||||
|
||||
enum E { A };
|
||||
|
||||
struct S {
|
||||
enum E { A };
|
||||
using E::A; // { dg-error "type .S. is not a base type for type .S." }
|
||||
using E::A; // { dg-error "not a base" "" { target { ! c++2a } } }
|
||||
// { dg-error "conflicts" "" { target c++2a } .-1 }
|
||||
};
|
||||
|
||||
namespace N {
|
||||
@@ -17,5 +26,5 @@ namespace N {
|
||||
}
|
||||
|
||||
struct T {
|
||||
using N::E::B; // { dg-error "using-declaration for non-member at class scope" }
|
||||
using N::E::B; // { dg-error "enum" "" { target { ! c++2a } } }
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace a {
|
||||
template <typename...> using c = b;
|
||||
}
|
||||
template <typename... d> struct e : a::c<d...> { // { dg-error "incomplete" }
|
||||
using a::c<>::c; // { dg-prune-output "not a base" }
|
||||
using a::c<>::c; // { dg-prune-output "not a direct base" }
|
||||
};
|
||||
template <template <typename> typename f> void g() { f(); }
|
||||
void h() { g<e>(); }
|
||||
|
||||
62
gcc/testsuite/g++.dg/cpp2a/using-enum-1.C
Normal file
62
gcc/testsuite/g++.dg/cpp2a/using-enum-1.C
Normal file
@@ -0,0 +1,62 @@
|
||||
// Test of using an enumerator.
|
||||
// { dg-do compile { target c++2a } }
|
||||
|
||||
// using ENUM::V;
|
||||
enum class E {v};
|
||||
|
||||
using E::v;
|
||||
using E::v; // OK
|
||||
|
||||
E a = v;
|
||||
|
||||
class C
|
||||
{
|
||||
using E::v; // { dg-message "declared private here" }
|
||||
|
||||
static inline const E m = v;
|
||||
};
|
||||
|
||||
E b = C::v; // { dg-error "private" }
|
||||
|
||||
struct B
|
||||
{
|
||||
enum E {e};
|
||||
enum class EC {f};
|
||||
using EC::f;
|
||||
};
|
||||
|
||||
struct D
|
||||
{
|
||||
private:
|
||||
using B::e; // { dg-message "declared private here" }
|
||||
using B::f; // { dg-message "declared private here" }
|
||||
};
|
||||
|
||||
struct F : D
|
||||
{
|
||||
static inline const auto bad1 = e; // { dg-error "private" }
|
||||
static inline const auto bad2 = f; // { dg-error "private" }
|
||||
|
||||
static inline const auto ok1 = B::e;
|
||||
static inline const auto ok2 = B::f;
|
||||
static inline const auto also_ok1 = B::E::e;
|
||||
static inline const auto also_ok2 = B::EC::f;
|
||||
};
|
||||
|
||||
using B::e;
|
||||
auto bob = e;
|
||||
|
||||
struct Q
|
||||
{
|
||||
using B::e;
|
||||
};
|
||||
using Q::e; // OK
|
||||
|
||||
using D::e; // { dg-error "private" }
|
||||
|
||||
template <class T>
|
||||
struct X : T
|
||||
{
|
||||
using T::e;
|
||||
};
|
||||
auto fob = X<Q>::e;
|
||||
48
gcc/testsuite/g++.dg/cpp2a/using-enum-2.C
Normal file
48
gcc/testsuite/g++.dg/cpp2a/using-enum-2.C
Normal file
@@ -0,0 +1,48 @@
|
||||
// Test of 'using enum' in different scopes.
|
||||
// { dg-do compile { target c++20 } }
|
||||
|
||||
namespace N
|
||||
{
|
||||
enum class E { e, f };
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
using enum N::E;
|
||||
static_assert (e < f);
|
||||
}
|
||||
|
||||
struct A
|
||||
{
|
||||
using enum N::E;
|
||||
static_assert (e < f);
|
||||
};
|
||||
|
||||
namespace M
|
||||
{
|
||||
using enum N::E;
|
||||
static_assert (e < f);
|
||||
|
||||
enum class X: int; // { dg-message "opaque" }
|
||||
using enum X; // { dg-error "enum-specifier" }
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void f()
|
||||
{
|
||||
using enum N::E;
|
||||
static_assert (e < f);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct AT
|
||||
{
|
||||
using enum N::E;
|
||||
static_assert (e < f);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct BT
|
||||
{
|
||||
using enum T::E; // { dg-error "dependent" }
|
||||
};
|
||||
6
gcc/testsuite/g++.dg/cpp2a/using-enum-3.C
Normal file
6
gcc/testsuite/g++.dg/cpp2a/using-enum-3.C
Normal file
@@ -0,0 +1,6 @@
|
||||
// Test of 'using enum' syntax error recovery.
|
||||
// { dg-do compile { target c++20 } }
|
||||
|
||||
using enum 2 + garbage3'850%^&; // { dg-error "" }
|
||||
|
||||
void f() {}
|
||||
13
gcc/testsuite/g++.dg/cpp2a/using-enum-4.C
Normal file
13
gcc/testsuite/g++.dg/cpp2a/using-enum-4.C
Normal file
@@ -0,0 +1,13 @@
|
||||
// Test for suggestion to try 'using enum'.
|
||||
// { dg-do compile { target c++20 } }
|
||||
|
||||
struct A
|
||||
{
|
||||
enum E { e };
|
||||
};
|
||||
|
||||
struct B
|
||||
{
|
||||
using A::E; // { dg-error "" }
|
||||
// { dg-message "using enum" "" { target *-*-* } .-1 }
|
||||
};
|
||||
132
gcc/testsuite/g++.dg/cpp2a/using-enum-5.C
Normal file
132
gcc/testsuite/g++.dg/cpp2a/using-enum-5.C
Normal file
@@ -0,0 +1,132 @@
|
||||
// Examples from P1099R5
|
||||
// { dg-do compile { target c++20 } }
|
||||
|
||||
namespace my_lib {
|
||||
|
||||
enum class errcode
|
||||
{
|
||||
SUCCESS = 0,
|
||||
ENOMEM = 1,
|
||||
EAGAIN = 2,
|
||||
ETOOSLOW = 3
|
||||
};
|
||||
using enum errcode; // import enumerators into namespace
|
||||
}
|
||||
|
||||
namespace NS {
|
||||
my_lib::errcode get_widget() {
|
||||
using namespace my_lib;
|
||||
return ETOOSLOW; // works, and conversions to int don't.
|
||||
int i = ETOOSLOW; // { dg-error "" }
|
||||
}
|
||||
}
|
||||
|
||||
enum class rgba_color_channel { red, green, blue, alpha};
|
||||
|
||||
const char * to_string(rgba_color_channel channel) {
|
||||
switch (channel) {
|
||||
using enum rgba_color_channel;
|
||||
case red: return "red";
|
||||
case green: return "green";
|
||||
case blue: return "blue";
|
||||
case alpha: return "alpha";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
namespace ns {
|
||||
struct E_detail {
|
||||
enum E { e1, e2 };
|
||||
friend void swap(E&, E&); // adl-only swap in the only associated scope of the enum
|
||||
};
|
||||
using E = E_detail::E; // import E into ns
|
||||
using enum E; // expose the enumerators of E in ns. Also note the direct reference to E.
|
||||
}
|
||||
|
||||
int main() {
|
||||
auto x = ns::e1;
|
||||
auto y = ns::e2;
|
||||
swap(x, y); // finds the swap in the associated struct
|
||||
}
|
||||
|
||||
namespace N0 {
|
||||
enum E { x };
|
||||
struct S {
|
||||
enum H { y };
|
||||
enum class K { z };
|
||||
using E::x; // OK, introduces x into S
|
||||
using E::x; // { dg-error "" } redeclaration in class scope
|
||||
using H::y; // { dg-error "" } redeclaration in class scope
|
||||
using K::z; // OK, introduces z into S
|
||||
};
|
||||
namespace NS {
|
||||
enum H { y };
|
||||
enum class K { z };
|
||||
using E::x; // OK, introduces x into NS
|
||||
using E::x; // OK, just a redeclaration of the same entity
|
||||
using H::y; // OK, redeclaration of the same entity
|
||||
using K::z; // OK, introduces z into NS
|
||||
};
|
||||
}
|
||||
namespace N1 {
|
||||
struct S {
|
||||
enum E { x };
|
||||
enum class EC { y };
|
||||
using EC::y;
|
||||
};
|
||||
|
||||
void f() {
|
||||
using S::x; // OK
|
||||
x; // resolves to S::E::x;
|
||||
using S::y; // OK
|
||||
y; // resolves to S::EC::y;
|
||||
}
|
||||
}
|
||||
|
||||
namespace N2 {
|
||||
enum class E { a, b, c };
|
||||
using E::a, E::b, E::c; // OK, imports all three
|
||||
auto x = (a,b,c);
|
||||
}
|
||||
|
||||
namespace N3 {
|
||||
struct B {
|
||||
enum class E { x };
|
||||
};
|
||||
enum class H { y };
|
||||
struct C : B {
|
||||
using enum B::E; // OK, introduces E::x into C
|
||||
using enum H; // OK, introduces y into C. Does not introduce H
|
||||
};
|
||||
auto i = C::y; // OK
|
||||
C::H h; // { dg-error "" }
|
||||
}
|
||||
|
||||
namespace N4 {
|
||||
enum class button { up, down };
|
||||
struct S {
|
||||
using button::up;
|
||||
button b = up; // OK
|
||||
};
|
||||
}
|
||||
|
||||
namespace N5 {
|
||||
enum class fruit { orange, apple };
|
||||
struct S {
|
||||
using enum fruit; // OK, introduces orange and apple into S
|
||||
};
|
||||
void f() {
|
||||
S s;
|
||||
s.orange; // OK, names fruit::orange
|
||||
S::orange; // OK, names fruit::orange
|
||||
}
|
||||
}
|
||||
|
||||
namespace N6 {
|
||||
enum class fruit { orange, apple };
|
||||
enum class color { red, orange };
|
||||
void f() {
|
||||
using enum fruit; // OK
|
||||
using enum color; // { dg-error "" } color::orange and fruit::orange conflict
|
||||
}
|
||||
}
|
||||
5
gcc/testsuite/g++.dg/cpp2a/using-enum-6.C
Normal file
5
gcc/testsuite/g++.dg/cpp2a/using-enum-6.C
Normal file
@@ -0,0 +1,5 @@
|
||||
// { dg-do compile { target c++2a } }
|
||||
|
||||
using enum void; // { dg-error "non-enum" }
|
||||
struct A {}; // { dg-message "declared here" }
|
||||
using enum A; // { dg-error "non-enum" }
|
||||
21
gcc/testsuite/g++.dg/debug/dwarf2/using-enum.C
Normal file
21
gcc/testsuite/g++.dg/debug/dwarf2/using-enum.C
Normal file
@@ -0,0 +1,21 @@
|
||||
// Test of 'using enum' debug info.
|
||||
// { dg-do compile { target c++20 } }
|
||||
// { dg-options "-g -dA" }
|
||||
|
||||
struct A
|
||||
{
|
||||
// All the counts are +1 for the abbreviation table.
|
||||
// { dg-final { scan-assembler-times "DW_TAG_enumeration_type" 2 } }
|
||||
// { dg-final { scan-assembler-times "DW_TAG_enumerator" 3 } }
|
||||
enum E { e, f };
|
||||
};
|
||||
|
||||
struct B
|
||||
{
|
||||
// The using-enum-declaration is represented by two
|
||||
// DW_TAG_imported_declaration, one for each enumerator.
|
||||
// { dg-final { scan-assembler-times "DW_TAG_imported_declaration" 3 } }
|
||||
using enum A::E;
|
||||
};
|
||||
|
||||
B b;
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
template<int> struct A
|
||||
{
|
||||
A::A; // { dg-error "constructor|not a base" }
|
||||
A::A; // { dg-error "constructor|not a direct base" }
|
||||
};
|
||||
|
||||
struct B
|
||||
|
||||
Reference in New Issue
Block a user