On Fri, Dec 19, 2025 at 10:26:58AM +0700, Jason Merrill wrote:
> On 12/18/25 12:43 AM, Marek Polacek wrote:
> > On Wed, Dec 17, 2025 at 11:42:41AM -0500, Marek Polacek wrote:
> > @@ -6320,18 +6343,27 @@ cp_parser_splice_expression (cp_parser *parser,
> > bool template_p,
> > {
> > /* Grab the unresolved expression then. */
> > t = unresolved;
> > - if (DECL_P (t))
> > - /* We cannot forget what context we came from, so build up
> > - a SCOPE_REF. */
> > - t = build_qualified_name (/*type=*/NULL_TREE, CP_DECL_CONTEXT (t),
> > - DECL_NAME (t), /*template_p=*/false);
> > - else if (OVL_P (t))
> > - t = OVL_NAME (t);
> > - gcc_assert (TREE_CODE (t) == SCOPE_REF
> > - || BASELINK_P (t)
> > - || TREE_CODE (t) == SPLICE_EXPR
> > - || TREE_CODE (t) == TEMPLATE_ID_EXPR
> > - || TREE_CODE (t) == TREE_BINFO);
> > + if (TREE_CODE (t) == FIELD_DECL
> > + || VAR_P (t)
> > + || TREE_CODE (t) == CONST_DECL
> > + || TREE_CODE (t) == FUNCTION_DECL
> > + || DECL_FUNCTION_TEMPLATE_P (OVL_FIRST (t)))
> > + ;
> > + else
> > + {
> > + if (DECL_P (t))
> > + /* We cannot forget what context we came from, so build up
> > + a SCOPE_REF. */
> > + t = build_qualified_name (/*type=*/NULL_TREE, CP_DECL_CONTEXT (t),
> > + DECL_NAME (t), /*template_p=*/false);
>
> What decls still get here?
This, and...
> > + else if (OVL_P (t))
> > + t = OVL_NAME (t);
>
> Or here? Seems like OVL_NAME will never produce something that satisfies
> the assert just below.
...this is no longer present: removed in
<https://forge.sourceware.org/marek/gcc/commit/59af6f17962d951e98ee17a62e12f80079797e40>
> > @@ -19325,8 +19287,7 @@ cp_parser_decltype_expr (cp_parser *parser,
> > /* [dcl.type.decltype] "if E is an unparenthesized
> > splice-expression,
> > decltype(E) is the type of the entity, object, or value designated
> > by the splice-specifier of E" */
> > - if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE)
> > - && !cp_parser_splice_spec_is_nns_p (parser))
> > + if (cp_parser_nth_token_starts_splice_without_nns_p (parser, 1))
> > {
> > cp_id_kind idk;
> > expr = cp_parser_splice_expression (parser, /*template_p=*/false,
> > @@ -22279,30 +22240,25 @@ cp_parser_type_specifier (cp_parser* parser,
> > /* Fall through. */
> > case RID_TYPENAME:
> > - {
> > - /* If we see 'typename [:', this could be a typename-specifier.
> > - But if there's no '::' after the '[:x:]' then it is probably
>
> Maybe drop "probably"? Can it be anything else?
I don't think that it can be anything else in a valid program. I'm
going to drop the "probably" in the patch below.
> > - a simple-type-specifier. */
> > - if (keyword == RID_TYPENAME
> > - && cp_lexer_peek_nth_token (parser->lexer, 2)->type
> > - == CPP_OPEN_SPLICE
> > - && !cp_parser_splice_spec_is_nns_p (parser))
> > - break;
> > -
> > - /* Look for an elaborated-type-specifier. */
> > - type_spec
> > - = (cp_parser_elaborated_type_specifier
> > - (parser,
> > - decl_spec_seq_has_spec_p (decl_specs, ds_friend),
> > - is_declaration));
> > -
> > - if (decl_specs)
> > - cp_parser_set_decl_spec_type (decl_specs,
> > - type_spec,
> > - token,
> > - /*type_definition_p=*/false);
> > - return type_spec;
> > - }
> > + /* If we see 'typename [:', this could be a typename-specifier.
> > + But if there's no '::' after the '[:x:]' then it is probably
> > + a simple-type-specifier. */
> > + if (keyword == RID_TYPENAME
> > + && cp_parser_nth_token_starts_splice_without_nns_p (parser, 2))
> > + break;
> > +
> > + /* Look for an elaborated-type-specifier. */
> > + type_spec
> > + = (cp_parser_elaborated_type_specifier
> > + (parser,
> > + decl_spec_seq_has_spec_p (decl_specs, ds_friend),
> > + is_declaration));
> > + if (decl_specs)
> > + cp_parser_set_decl_spec_type (decl_specs,
> > + type_spec,
> > + token,
> > + /*type_definition_p=*/false);
> > + return type_spec;
> > case RID_CONST:
> > ds = ds_const;
> > @@ -24389,8 +24345,7 @@ cp_parser_namespace_alias_definition (cp_parser*
> > parser)
> > return;
> > }
> > cp_parser_require (parser, CPP_EQ, RT_EQ);
> > - if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE)
> > - && !cp_parser_splice_spec_is_nns_p (parser))
> > + if (cp_parser_nth_token_starts_splice_without_nns_p (parser, 1))
> > namespace_specifier = cp_parser_splice_specifier (parser);
> > else
> > /* Look for the qualified-namespace-specifier. */
> > @@ -24879,8 +24834,7 @@ cp_parser_using_directive (cp_parser* parser)
> > cp_parser_require_keyword (parser, RID_USING, RT_USING);
> > /* And the `namespace' keyword. */
> > cp_parser_require_keyword (parser, RID_NAMESPACE, RT_NAMESPACE);
> > - if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE)
> > - && !cp_parser_splice_spec_is_nns_p (parser))
> > + if (cp_parser_nth_token_starts_splice_without_nns_p (parser, 1))
> > namespace_decl = cp_parser_splice_specifier (parser);
> > else
> > {
> > @@ -27653,15 +27607,25 @@ cp_parser_declarator_id (cp_parser* parser, bool
> > optional_p)
> > If IS_TRAILING_RETURN is true, we are in a trailing-return-type,
> > i.e. we've just seen "->".
> > + If TYPE_ALIAS_P is non-null, we set it to true or false depending
> > + on if the type-id is a type alias or just a type. E.g., given
> > +
> > + template<typename> struct S {};
> > + template<typename T> using A = const S<T>;
> > +
> > + ^^A<int> is a type alias, but ^^const A<int>, ^^A<int> const, or
> > ^^A<int>*
> > + are just types, not type aliases.
> > +
> > Returns the TYPE specified. */
> > static tree
> > cp_parser_type_id_1 (cp_parser *parser, cp_parser_flags flags,
> > bool is_template_arg, bool is_trailing_return,
> > - location_t *type_location)
> > + location_t *type_location, bool *type_alias_p)
> > {
> > cp_decl_specifier_seq type_specifier_seq;
> > cp_declarator *abstract_declarator;
> > + cp_token *next = nullptr;
> > /* Parse the type-specifier-seq. */
> > cp_parser_type_specifier_seq (parser, flags,
> > @@ -27671,6 +27635,17 @@ cp_parser_type_id_1 (cp_parser *parser,
> > cp_parser_flags flags,
> > if (type_location)
> > *type_location = type_specifier_seq.locations[ds_type_spec];
> > + /* If there is just ds_type_spec specified, this could be a type alias.
> > */
> > + if (type_alias_p && is_typedef_decl (type_specifier_seq.type))
> > + {
> > + int i;
> > + for (i = ds_first; i < ds_last; ++i)
> > + if (i != ds_type_spec && type_specifier_seq.locations[i])
> > + break;
> > + if (i == ds_last)
> > + next = cp_lexer_peek_token (parser->lexer);
> > + }
>
> Can we drop this (and the declaration of next) and...
>
> > if (is_template_arg && type_specifier_seq.type
> > && TREE_CODE (type_specifier_seq.type) == TEMPLATE_TYPE_PARM
> > && CLASS_PLACEHOLDER_TEMPLATE (type_specifier_seq.type))
> > @@ -27698,6 +27673,11 @@ cp_parser_type_id_1 (cp_parser *parser,
> > cp_parser_flags flags,
> > if (!cp_parser_parse_definitely (parser))
> > abstract_declarator = NULL;
> > + /* If we found * or & and similar after the type-specifier, it's not
> > + a type alias. */
> > + if (type_alias_p)
> > + *type_alias_p = cp_lexer_peek_token (parser->lexer) == next;
>
> ...check !abstract_declarator here instead of next? (And check
> is_typedef/type_specifier_seq here instead of above)
Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706571.html>
> > +static tree
> > +tsubst_splice_expr (tree t, tree args, tsubst_flags_t complain, tree
> > in_decl)
> > +{
> > + tree op = tsubst_expr (TREE_OPERAND (t, 0), args, complain, in_decl);
> > + if (op == error_mark_node)
> > + return error_mark_node;
> > + op = splice (op);
> > + if (dependent_splice_p (op))
> > + {
> > + if (SPLICE_EXPR_EXPRESSION_P (t))
> > + SET_SPLICE_EXPR_EXPRESSION_P (op);
> > + if (SPLICE_EXPR_MEMBER_ACCESS_P (t))
> > + SET_SPLICE_EXPR_MEMBER_ACCESS_P (op, true);
> > + if (SPLICE_EXPR_ADDRESS_P (t))
> > + SET_SPLICE_EXPR_ADDRESS_P (op, true);
> > + return op;
> > + }
> > + if (SPLICE_EXPR_EXPRESSION_P (t)
> > + && !check_splice_expr (input_location, UNKNOWN_LOCATION, op,
> > + SPLICE_EXPR_ADDRESS_P (t),
> > + SPLICE_EXPR_MEMBER_ACCESS_P (t),
> > + (complain & tf_error)))
> > + return error_mark_node;
> > + if (outer_automatic_var_p (op))
> > + op = process_outer_var_ref (op, complain);
> > + /* Like in cp_parser_splice_expression, for foo.[: bar :]
> > + cp_parser_postfix_dot_deref_expression wants to see only
> > + certain kind of entities. */
> > + if (SPLICE_EXPR_MEMBER_ACCESS_P (t))
> > + {
> > + if (TREE_CODE (op) == FIELD_DECL
> > + || VAR_P (op)
> > + || TREE_CODE (op) == CONST_DECL
> > + || TREE_CODE (op) == FUNCTION_DECL
> > + || DECL_FUNCTION_TEMPLATE_P (OVL_FIRST (op))
> > + || variable_template_p (op))
>
> Why is variable_template_p here but not in cp_parser_splice_expression?
Added into cp_parser_splice_expression in:
<https://forge.sourceware.org/marek/gcc/commit/59af6f17962d951e98ee17a62e12f80079797e40>
> > + ;
> > + else
> > + {
> > + if (DECL_P (op))
> > + /* If we are called from tsubst_expr/SCOPE_REF, we'll build
> > + the SCOPE_REF there. */
> > + op = DECL_NAME (op);
> > + else if (OVL_P (op))
> > + op = OVL_NAME (op);
>
> As in cp_parser_splice_expression, what cases do these handle?
Removed in
<https://forge.sourceware.org/marek/gcc/commit/59af6f17962d951e98ee17a62e12f80079797e40>
> > @@ -1350,6 +1362,7 @@ eval_is_expected_access (tree r, reflect_kind kind,
> > tree expected_access)
> > {
> > gcc_assert (TREE_CODE (r) == TREE_BINFO);
> > tree c = r;
> > + /* Looping needed for multiple virtual inheritance. */
> > while (BINFO_INHERITANCE_CHAIN (c))
> > c = BINFO_INHERITANCE_CHAIN (c);
>
> Since this comes up so many times, how about adding a function to return the
> derived type in an inheritance reflection?
We now have direct_base_derived_binfo.
> > @@ -8491,6 +8572,26 @@ check_splice_expr (location_t loc, location_t
> > start_loc, tree t,
> > return false;
> > }
> > + /* [expr.prim.splice]/2: "The expression is ill-formed if S [the
> > construct
> > + designated by splice-specifier] is
> > + -- a local entity such that there is a lambda scope that intervenes
> > + between the expression and the point at which S was introduced"
> > + This also checks ODR violations (reflect/odr1.C). */
> > + if (outer_automatic_var_p (t)
> > + && process_outer_var_ref (t, tf_none) == error_mark_node)
>
> This still seems not quite right; it doesn't say that any outer automatic
> variable is ill-formed, only if there's a lambda. Of course, any actual use
> is still ill-formed under the normal rules, so I guess this is close enough.
>
> But if we're in a capturing lambda, we also need to check that
> process_outer_var_ref doesn't return a capture.
Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706566.html>
> Apart from that small change, really getting lambdas right should probably
> involve refactoring process_outer_var_ref and also fixing 112926, and happen
> after the merge.
Sounds like GCC 17 project. Maybe I can take that since I worked on P2036R3
a few months ago.
> > + {
> > + /* Not letting process_outer_var_ref emit the error so that we can
> > + say "in a splice expression". */
> > + if (complain_p)
> > + {
> > + auto_diagnostic_group d;
> > + error_at (loc, "use of local variable with automatic storage from "
> > + "containing function in a splice expression");
> > + inform (DECL_SOURCE_LOCATION (t), "%q#D declared here", t);
> > + }
> > + return false;
> > + }
> > +
> > /* If we had a reflect_kind here, we could just check for
> > REFLECT_ANNOTATION and be done with it. But we don't have it yet
> > (TODO),
> > so do it the suboptimal way. */
> > @@ -8552,7 +8653,7 @@ tree
> > reflection_mangle_prefix (tree refl, char prefix[3])
> > {
> > tree h = REFLECT_EXPR_HANDLE (refl);
> > - reflect_kind kind = static_cast<reflect_kind>(REFLECT_EXPR_KIND (refl));
> > + reflect_kind kind = REFLECT_EXPR_KIND (refl);
> > if (h == unknown_type_node)
> > {
> > strcpy (prefix, "nu");
> > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> > index 2d08bda923a..5b645aa5568 100644
> > --- a/gcc/cp/semantics.cc
> > +++ b/gcc/cp/semantics.cc
> > @@ -4529,7 +4529,7 @@ baselink_for_fns (tree fns)
> > /* Returns true iff we are currently parsing a lambda-declarator. */
> > -static bool
> > +bool
> > parsing_lambda_declarator ()
> > {
> > cp_binding_level *b = current_binding_level;
> > @@ -13686,16 +13686,13 @@ fold_builtin_is_string_literal (location_t loc,
> > int nargs, tree *args)
> > STRIP_NOPS (arg);
> > while (TREE_CODE (arg) == POINTER_PLUS_EXPR)
> > {
> > - if (TREE_CODE (TREE_OPERAND (arg, 1)) != INTEGER_CST)
> > - return boolean_false_node;
> > arg = TREE_OPERAND (arg, 0);
> > STRIP_NOPS (arg);
> > }
> > if (TREE_CODE (arg) != ADDR_EXPR)
> > return boolean_false_node;
> > arg = TREE_OPERAND (arg, 0);
> > - if (TREE_CODE (arg) == ARRAY_REF
> > - && TREE_CODE (TREE_OPERAND (arg, 1)) == INTEGER_CST)
> > + if (TREE_CODE (arg) == ARRAY_REF)
> > arg = TREE_OPERAND (arg, 0);
> > if (TREE_CODE (arg) != STRING_CST)
> > return boolean_false_node;
> > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> > index 5fb797e527d..e69b1643777 100644
> > --- a/gcc/cp/tree.cc
> > +++ b/gcc/cp/tree.cc
> > @@ -5844,14 +5844,34 @@ handle_annotation_attribute (tree *node, tree
> > ARG_UNUSED (name),
> > *no_add_attrs = true;
> > return NULL_TREE;
> > }
> > - if (!type_dependent_expression_p (TREE_VALUE (args))
> > - && !structural_type_p (TREE_TYPE (TREE_VALUE (args))))
> > + if (!type_dependent_expression_p (TREE_VALUE (args)))
> > {
> > - auto_diagnostic_group d;
> > - error ("annotation does not have structural type");
> > - structural_type_p (TREE_TYPE (TREE_VALUE (args)), true);
> > - *no_add_attrs = true;
> > - return NULL_TREE;
> > + if (!structural_type_p (TREE_TYPE (TREE_VALUE (args))))
> > + {
> > + auto_diagnostic_group d;
> > + error ("annotation does not have structural type");
> > + structural_type_p (TREE_TYPE (TREE_VALUE (args)), true);
> > + *no_add_attrs = true;
> > + return NULL_TREE;
> > + }
> > + if (CLASS_TYPE_P (TREE_TYPE (TREE_VALUE (args))))
> > + {
> > + tree arg = make_tree_vec (1);
> > + tree type = TREE_TYPE (TREE_VALUE (args));
> > + tree ctype
> > + = cp_build_qualified_type (type, cp_type_quals (type)
> > + | TYPE_QUAL_CONST);
> > + TREE_VEC_ELT (arg, 0)
> > + = cp_build_reference_type (ctype, /*rval=*/false);
>
> Maybe build_stub_type would be useful?
Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706570.html>
and committed.
> > + if (!is_xible (INIT_EXPR, type, arg))
> > + {
> > + auto_diagnostic_group d;
> > + error ("annotation does not have copy constructible type");
> > + is_xible (INIT_EXPR, type, arg, /*explain=*/true);
> > + *no_add_attrs = true;
> > + return NULL_TREE;
> > + }
> > + }
> > }
> > if (!processing_template_decl)
> > {
> > @@ -6787,6 +6807,14 @@ maybe_adjust_arg_pos_for_attribute (const_tree
> > fndecl)
> > return n > 0 ? n - 1 : 0;
> > }
> > +/* True if ATTR is annotation. */
> > +
> > +bool
> > +annotation_p (tree attr)
> > +{
> > + return is_attribute_p ("annotation ", get_attribute_name (attr));
>
> Should this also check the namespace? Well, perhaps that's not important
> for a name that a user can't write.
No change here.
> > +}
> > +
> >
> > /* Release memory we no longer need after parsing. */
> > void
> > diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> > index 3f471b47876..beee605d0b7 100644
> > --- a/gcc/cp/typeck.cc
> > +++ b/gcc/cp/typeck.cc
> > @@ -3443,9 +3443,9 @@ complain_about_unrecognized_member (tree access_path,
> > tree name,
> > /* This function is called by the parser to process a class member
> > access expression of the form OBJECT.NAME. NAME is a node used by
> > - the parser to represent a name; it is not yet a DECL. It may,
> > - however, be a BASELINK where the BASELINK_FUNCTIONS is a
> > - TEMPLATE_ID_EXPR. Templates must be looked up by the parser, and
> > + the parser to represent a name; it is not yet a DECL, except when
> > + SPLICE_P. It may, however, be a BASELINK where the BASELINK_FUNCTIONS
> > + is a TEMPLATE_ID_EXPR. Templates must be looked up by the parser, and
> > there is no reason to do the lookup twice, so the parser keeps the
> > BASELINK. TEMPLATE_P is true iff NAME was explicitly declared to
> > be a template via the use of the "A::template B" syntax. SPLICE_P
> > @@ -3498,8 +3498,10 @@ finish_class_member_access_expr (cp_expr object,
> > tree name, bool template_p,
> > && dependent_splice_p (TREE_OPERAND (name, 1))))
> > {
> > dependent:
> > - return build_min_nt_loc (UNKNOWN_LOCATION, COMPONENT_REF,
> > + expr = build_min_nt_loc (UNKNOWN_LOCATION, COMPONENT_REF,
> > orig_object, orig_name, NULL_TREE);
> > + COMPONENT_REF_SPLICE_P (expr) = splice_p;
> > + return expr;
> > }
> > }
> > else if (c_dialect_objc ()
> > @@ -3530,11 +3532,6 @@ finish_class_member_access_expr (cp_expr object,
> > tree name, bool template_p,
> > return error_mark_node;
> > }
> > - /* For OBJECT.[:S::fn:] the BASELINK can be inside a SCOPE_REF.
> > - This happens, but, until Reflection, not for a class member access.
> > */
> > - if (TREE_CODE (name) == SCOPE_REF && BASELINK_P (TREE_OPERAND (name, 1)))
> > - name = TREE_OPERAND (name, 1);
> > -
> > if (BASELINK_P (name))
> > /* A member function that has already been looked up. */
> > member = name;
> > @@ -3574,6 +3571,13 @@ finish_class_member_access_expr (cp_expr object,
> > tree name, bool template_p,
> > return error_mark_node;
> > }
> > }
> > + else if (splice_p
> > + && (TREE_CODE (name) == FIELD_DECL
> > + || VAR_P (name)
> > + || TREE_CODE (name) == CONST_DECL
> > + || TREE_CODE (name) == FUNCTION_DECL
> > + || DECL_FUNCTION_TEMPLATE_P (OVL_FIRST (name))))
>
> You can't just add these to the "already looked up" case above that was just
> BASELINK? (And perhaps do without splice_p?) It seems awkward to add a
> bunch of exceptions to the lookup code for something that's already looked
> up, probably better to add any needed checking (e.g. for dependent
> attributes) to the already-looked-up case.
I see what you mean. But we need to compute access_path for the
build_baselink call in a splice_p block. I suppose I could factor out
the whole if (scope) block and then move the splice_p code to the
"already looked up" case above. Does that sound reasonable?
> > diff --git a/gcc/testsuite/g++.dg/reflect/dep5.C
> > b/gcc/testsuite/g++.dg/reflect/dep5.C
> > index c8d84b51c05..4a118de1b70 100644
> > --- a/gcc/testsuite/g++.dg/reflect/dep5.C
> > +++ b/gcc/testsuite/g++.dg/reflect/dep5.C
> > @@ -18,8 +18,8 @@ f ()
> > static_assert ([: ^^T :]::x == 42);
> > typename [: ^^T :]::type a = 42;
> > [: ^^T :]::fn (42);
> > - [: ^^T :]::template tfn<([: ^^T :])>(); // { dg-error ".S. is not
> > usable in a splice expression" }
> > - auto x = [: ^^T :]::template var<([: ^^T :])>; // { dg-error ".S. is
> > not usable in a splice expression" }
> > + [: ^^T :]::template tfn<([: ^^T :])>(); // { dg-error "expected a
> > reflection of an expression instead of type .S." }
> > + auto x = [: ^^T :]::template var<([: ^^T :])>; // { dg-error "expected
> > a reflection of an expression instead of type .S." }
>
> This rephrasing seems like an improvement, can we do the same with the other
> "not usable" diagnostics?
Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-February/708160.html>
> > }
> > void
> > diff --git a/gcc/testsuite/g++.dg/reflect/diag1.C
> > b/gcc/testsuite/g++.dg/reflect/diag1.C
> > index 3792c949bc5..2d34623a8ac 100644
> > --- a/gcc/testsuite/g++.dg/reflect/diag1.C
> > +++ b/gcc/testsuite/g++.dg/reflect/diag1.C
> > @@ -14,12 +14,12 @@ void
> > f ()
> > {
> > S s;
> > - s.[: ^^S::tfn :](42); // { dg-error "reflection not usable in a splice
> > expression" }
> > + s.[: ^^S::tfn :](42); // { dg-error "reflection .S::tfn. not usable in a
> > splice expression" }
> > // { dg-message "add .template. to denote a template" "" { target *-*-* }
> > .-1 }
> > s.template [: ^^S::tfn :](42);
> > constexpr auto r = ^^fortytwo;
> > - constexpr int i1 = [:r:]<int>; // { dg-error "reflection not usable in a
> > splice expression with template arguments" }
> > + constexpr int i1 = [:r:]<int>; // { dg-error "reflection .fortytwo<int>.
> > not usable in a splice expression with template arguments" }
>
> This diagnostic is pretty confusing (the reflection is not of
> fortytwo<int>), it should only suggest the missing 'template' keyword. And
> in this case since we already decide the <> are template arguments the
> diagnostic could be a pedwarn.
Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-February/708160.html>
> Though also, cp_parser_splice_specifier seems to be too eagerly concluding
> that :]< is a template-id; what about
>
> int i;
> constexpr auto r = ^^i;
> bool b = [:r:] < 42;
>
> ? The missing_template_diag code seems relevant.
Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706989.html>
> > // { dg-message "add .template. to denote a template" "" { target *-*-* }
> > .-1 }
> > constexpr int i2 = template [:r:]<int>;
> > }
> > diff --git a/gcc/testsuite/g++.dg/reflect/enumerators_of1.C
> > b/gcc/testsuite/g++.dg/reflect/enumerators_of1.C
> > index 6896fa97230..17141185afa 100644
> > --- a/gcc/testsuite/g++.dg/reflect/enumerators_of1.C
> > +++ b/gcc/testsuite/g++.dg/reflect/enumerators_of1.C
> > @@ -104,7 +104,7 @@ qux ()
> > // We certainly during instantiation don't differentiate between
> > // forward enum declarations and later definitions.
>
> Yeah, it would be pretty unfriendly to require us to represent such a
> useless distinction.
No change.
> > diff --git a/gcc/testsuite/g++.dg/reflect/extract1.C
> > b/gcc/testsuite/g++.dg/reflect/extract1.C
> > index c3550cb8bd2..6eaf9ffe4b9 100644
> > --- a/gcc/testsuite/g++.dg/reflect/extract1.C
> > +++ b/gcc/testsuite/g++.dg/reflect/extract1.C
> > @@ -25,11 +25,11 @@ constexpr auto rA = reflect_constant ('A');
> > static_assert (extract<char>(rA) == 'A');
> > static_assert (extract<const char>(rA) == 'A');
> > constexpr auto rPIf = reflect_constant (3.14f);
> > -static_assert (extract<float>(rPIf) == 3.14f);
> > -static_assert (extract<const float>(rPIf) == 3.14f);
> > +static_assert (extract<float>(rPIf) == float(3.14f));
> > +static_assert (extract<const float>(rPIf) == float(3.14f));
> > constexpr auto rPI = reflect_constant (3.14);
> > -static_assert (extract<double>(rPI) == 3.14);
> > -static_assert (extract<const double>(rPI) == 3.14);
> > +static_assert (extract<double>(rPI) == double(3.14));
> > +static_assert (extract<const double>(rPI) == double(3.14));
> > constexpr auto r666 = reflect_constant (666L);
> > static_assert (extract<long int>(r666) == 666L);
> > static_assert (extract<const long int>(r666) == 666L);
> > @@ -42,10 +42,16 @@ static_assert (extract<const signed char>(constant_of
> > (^^sc)) == -1);
> > constexpr unsigned char uc = 255;
> > static_assert (extract<unsigned char>(constant_of (^^uc)) == 255);
> > static_assert (extract<const unsigned char>(constant_of (^^uc)) == 255);
> > -constexpr const int *p = nullptr;
> > +constexpr int *p = nullptr;
> > constexpr info rp = ^^p;
> > +static_assert (extract<int *>(rp) == nullptr);
> > +static_assert (extract<int * const>(rp) == nullptr);
> > static_assert (extract<const int *>(rp) == nullptr);
> > static_assert (extract<const int *const>(rp) == nullptr);
> > +constexpr const int *cp = nullptr;
> > +constexpr info rcp = ^^cp;
> > +static_assert (extract<const int *>(rcp) == nullptr);
> > +static_assert (extract<const int *const>(rcp) == nullptr);
> > constexpr void (*pfn0)() = nullptr;
> > constexpr void (*pfn1)(int) = nullptr;
> > constexpr info rpfn0 = ^^pfn0;
> > @@ -138,6 +144,9 @@ static_assert (roundtrip ([] {}));
> > struct B {
> > static constexpr int k = 42;
> > + int m;
> > + int* p;
> > +
> > int fn ();
> > int fn2 () noexcept;
> > static int fn3 ();
> > @@ -162,3 +171,13 @@ constexpr auto a4 = extract<int (*)()
> > noexcept>(^^B::fn4);
> > constexpr auto a5 = extract<int (*)(B)>(^^B::fn5);
> > constexpr auto a6 = extract<int (B::*)(int) &>(^^B::fn6);
> > constexpr auto a7 = extract<int (B::*)(int) &&>(^^B::fn7);
> > +
> > +constexpr auto a8 = extract<int B::*>(^^B::m);
> > +constexpr auto a9 = extract<int const B::*>(^^B::m);
> > +constexpr auto a10 = extract<int* B::*>(^^B::p);
> > +constexpr auto a11 = extract<int* const B::*>(^^B::p);
> > +constexpr auto a12 = extract<int const* const B::*>(^^B::p);
> > +
> > +// FIXME removing noexcept should be allowed
>
> Please open a BZ if this isn't fixed before merge. And in general xfail
> rather than comment or #if 0.
Fixed by:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706546.html>
> > --- a/gcc/testsuite/g++.dg/reflect/member1.C
> > +++ b/gcc/testsuite/g++.dg/reflect/member1.C
> > @@ -55,9 +55,9 @@ f ()
> > sp->template [: ^^S::tfn :](42);
> > s.template [: ^^S::tfn :]<int>(42);
> > sp->template [: ^^S::tfn :]<int>(42);
> > - s.[: ^^S::var :]<int> = 1; // { dg-error "reflection not usable in a
> > splice expression with template arguments" }
> > + s.[: ^^S::var :]<int> = 1; // { dg-error "reflection .var<int>. not
> > usable in a splice expression with template arguments" }
> > s.template [: ^^S::var :]<int> = 1;
> > - sp->[: ^^S::var :]<int> = 1; // { dg-error "reflection not usable in a
> > splice expression with template arguments" }
> > + sp->[: ^^S::var :]<int> = 1; // { dg-error "reflection .var<int>. not
> > usable in a splice expression with template arguments" }
> > sp->template [: ^^S::var :]<int> = 1;
> > s.[: ^^S::b :].[: ^^B::a :].val;
> > sp->[: ^^S::b :].[: ^^B::a :].val;
> > @@ -74,9 +74,9 @@ f ()
> > cp->template [: ^^C<int>::tfn :](42);
> > c.template [: ^^C<int>::tfn :]<int>(42);
> > cp->template [: ^^C<int>::tfn :]<int>(42);
> > - c.[: ^^C<int>::var :]<int> = 1; // { dg-error "reflection not usable in
> > a splice expression with template arguments" }
> > + c.[: ^^C<int>::var :]<int> = 1; // { dg-error "reflection .var<int>. not
> > usable in a splice expression with template arguments" }
> > c.template [: ^^C<int>::var :]<int> = 1;
> > - cp->[: ^^C<int>::var :]<int> = 1; // { dg-error "reflection not usable
> > in a splice expression with template arguments" }
> > + cp->[: ^^C<int>::var :]<int> = 1; // { dg-error "reflection .var<int>.
> > not usable in a splice expression with template arguments" }
> > cp->template [: ^^C<int>::var :]<int> = 1;
> > c.[: ^^C<int>::b :].[: ^^B::a :].val;
> > cp->[: ^^C<int>::b :].[: ^^B::a :].val;
> > @@ -85,14 +85,15 @@ f ()
> > [: ^^S :]::template N<int> n1;
> > [: ^^C<int> :]::template N<int> n2;
> > - [: ^^C :]<int>::template N<int> n3;
> > + [: ^^C :]<int>::template N<int> n3; // { dg-error "missing
> > .template.|expected" }
> > + typename [: ^^C :]<int>::template N<int> n4;
> > s->[: ^^S::x :] = 2; // { dg-error "non-pointer type" }
> > sp.[: ^^S::x :] = 2; // { dg-error "which is of pointer type" }
> > c.[: ^^C<char>::x :] = 1; // { dg-error "is not a base of" }
> > cp->[: ^^C<char>::x :] = 1; // { dg-error "is not a base of" }
> > - s.template [: ^^S::N :].t; // { dg-error "reflection not usable in a
> > template splice" }
> > + s.template [: ^^S::N :].t; // { dg-error "reflection .S::N. not usable
> > in a template splice" }
>
> But S::N is a template, the problem is the missing targs.
Adding the targs wouldn't make this valid, because we expect a reflection
of an expression. We now say:
expected a reflection of a function template instead of class template 'S::N'
> > S::template [: ^^S::N<int> :] e1; // { dg-error "expected
> > unqualified-id" }
> > C<int>::template [: ^^S::N<int> :] e2; // { dg-error "expected
> > unqualified-id" }
>
> This should clarify that the problem is using the 'template' keyword with a
> splice of a specialization.
I didn't change this, sorry. I think the current error is fine -- this
code would still be invalid even without 'template'. The problem is that
we arrive at the
qualified-id:
nested-name-specifier template[opt] unqualified-id
production but unqualified-id doesn't expand to a splice (only after ~).
So we error in cp_parser_unqualified_id because the next token is
CPP_OPEN_SPLICE which we don't handle there.
Marek