On Monday, February 9th, 2026 at 5:41 PM, Marek Polacek <[email protected]> 
wrote:

> 
> 
> On Mon, Feb 09, 2026 at 11:03:14PM +0900, Jason Merrill wrote:
> 
> > On 2/8/26 6:43 PM, Boris Staletic wrote:
> > 
> > > Bootstrapped and tested on x86_64-pc-linux-gnu.
> > > -- >8 --
> > > In case of expressions like `&[:expr:]` where `expr` depends on a
> > > template parameter, and the splice expression represents a `FIELD_DECL` or
> > > a non-static member `FUNCTION_DECL`, that's exactly what we'd pass on.
> > > However, `build_x_unary_op()` for these expressions is expecting an
> > > `OFFSET_REF`. `OFFSET_REF` is also what gets passed to
> > > `build_x_unary_op()` when templates are not involved.
> > > 
> > > There's also a difference between the template argument being a type and
> > > using `members_of()` to get to the reflections of members (in which case
> > > evaluating the `SPLICE_EXPR` returns a `FUNCTION_DECL` - `splice8.C`
> > > test) and passing `^^T::member` as the template argument (in which case
> > > evaluating the `SPLICE_EXPR` returns a `BASELINK` - `splice9.C`).
> > 
> > Thanks for the patch! A few commments below.
> 
> 
> Also thanks for your bug reports! Note that the PR is in the ASSIGNED
> state indicating that someone, in this case me, is already working on
> the problem. I didn't get to this PR yet so it's fine -- I thought it
> would depend on another bug fix that is on my plate, namely, not passing
> ctx to finish_id_expression. Looks like it doesn't.
> 

I did notice that the bug report had the ASSIGNED state, but I thought you
already had a lot to do and my intention was to help out.
I first posted this patch to the bug tracker, but after no replies I thought
it would have been fine to submit the patch for review.

> > > 2026-02-08 Boris Staletic [email protected]
> > > 
> > > PR c++/123660
> > > PR c++/123661
> > > * pt.cc (tsubst_expr): Handle pointers to non-static members
> > > from splice expressions.
> > > * g++.dg/reflect/splice10.C: New test.
> > > * g++.dg/reflect/splice11.C: New test.
> 
> 
> Note that we have separate ChangeLogs for cp/ and for testsuite/.
> It's best to use gcc-mklog to auto-generate a ChangeLog skeleton.
> Feel free to contact me off-list.

I did notice that and thought I got the format right.
Anyway, fixed now.

> 
> > > Signed-off-by: Boris Staletic [email protected]
> > > ---
> > > gcc/cp/pt.cc | 23 +++++++++++++++++--
> > > gcc/testsuite/g++.dg/reflect/splice10.C | 30 +++++++++++++++++++++++++
> > > gcc/testsuite/g++.dg/reflect/splice11.C | 29 ++++++++++++++++++++++++
> > > 3 files changed, 80 insertions(+), 2 deletions(-)
> > > create mode 100644 gcc/testsuite/g++.dg/reflect/splice10.C
> > > create mode 100644 gcc/testsuite/g++.dg/reflect/splice11.C
> > > 
> > > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > > index 049bbf07e0..fb1cd386e8 100644
> > > --- a/gcc/cp/pt.cc
> > > +++ b/gcc/cp/pt.cc
> > > @@ -21454,8 +21454,27 @@ tsubst_expr (tree t, tree args, tsubst_flags_t 
> > > complain, tree in_decl)
> > > op1 = tsubst_qualified_id (op1, args, complain, in_decl,
> > > /done=/true, /address_p=/true);
> > > else
> > > - op1 = tsubst_non_call_postfix_expression (op1, args, complain,
> > > - in_decl);
> > > + {
> > > + tree old_op1 = op1;
> > > + op1 = tsubst_non_call_postfix_expression (op1, args, complain,
> > > + in_decl);
> > > + if (TREE_CODE (old_op1) == SPLICE_EXPR)
> > > + {
> > > + if (TREE_CODE (op1) == BASELINK)
> > > + {
> > > + tree fn = BASELINK_FUNCTIONS (op1);
> > > + if (TREE_CODE (fn) == FUNCTION_DECL &&
> > > + TREE_CODE (TREE_TYPE (op1) ) == METHOD_TYPE &&
> > > + !TREE_STATIC (op1))
> > > + op1 = fn;
> > > + }
> > 
> > It should be fine to just pass the BASELINK on to build_offset_ref. You can
> > use BINFO_TYPE (BINFO_ACCESS_BINFO as the type argument.
> 
> 
> BASELINK_ACCESS_BINFO

Done.

> 
> > > + if (TREE_CODE (op1) == FIELD_DECL ||
> > > + (TREE_CODE (op1) == FUNCTION_DECL &&
> > > + TREE_CODE (TREE_TYPE (op1)) == METHOD_TYPE &&
> > > + !TREE_STATIC (TREE_TYPE (op1))))
> > 
> > Operators like ||/&& go at the beginning of the line in our coding style.
> > 
> > > + op1 = build_offset_ref(DECL_CONTEXT(op1), op1, true, complain);
> > 
> > And space before (.
> 
> 
> I would like this to be moved to tsubst_splice_expr (and I guess check
> SPLICE_EXPR_ADDRESS_P).

Done.

> 
> > > + }
> > > + }
> > > RETURN (build_x_unary_op (input_location, ADDR_EXPR, op1,
> > > templated_operator_saved_lookups (t),
> > > complain|decltype_flag));
> > > diff --git a/gcc/testsuite/g++.dg/reflect/splice10.C 
> > > b/gcc/testsuite/g++.dg/reflect/splice10.C
> > > new file mode 100644
> > > index 0000000000..cbed421021
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/reflect/splice10.C
> > > @@ -0,0 +1,30 @@
> > > +// PR c++/123660
> > > +// PR c++/123661
> > > +// { dg-do run { target c++26 } }
> 
> 
> I don't think this test needs to be dg-run, dg-compile should be enough.
> 

I initially used dg-compile, but this pull request made me reconsider:
https://gcc.gnu.org/pipermail/gcc-patches/2026-February/707791.html

Anyway, changed to dg-dompile.

> > > +// { dg-additional-options "-freflection" }
> > > +
> > > +#include <meta>
> > > +
> > > +struct s {
> > > + int get_z(this s) { return 4; }
> > > + static int get_y() { return 4; }
> > > + int get_x() { return 3; }
> > > + int xx;
> > > + static int xxx;
> > > +};
> > > +
> > > +int s::xxx = 5;
> > > +
> > > +template<typename T, decltype(0uz) I>
> > > +constexpr auto test() {
> > > + constexpr auto ctx = std::meta::access_context::current();
> > > + return &[:members_of(^^T, ctx)[I]:];
> > > +}
> > > +
> > > +int main() {
> > > + static_assert(test<s, 0uz>() == &s::get_z);
> > > + static_assert(test<s, 1uz>() == &s::get_y);
> > > + static_assert(test<s, 2uz>() == &s::get_x);
> > > + static_assert(test<s, 3uz>() == &s::xx);
> > > + static_assert(test<s, 4uz>() == &s::xxx);
> > > +}
> > > diff --git a/gcc/testsuite/g++.dg/reflect/splice11.C 
> > > b/gcc/testsuite/g++.dg/reflect/splice11.C
> > > new file mode 100644
> > > index 0000000000..6568afed07
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/reflect/splice11.C
> > > @@ -0,0 +1,29 @@
> 
> 
> I'd also add // PR c++/123661 here.

Done.

> 
> > > +// { dg-do run { target c++26 } }
> 
> 
> Also dg-compile.

Done.

> 
> > > +// { dg-additional-options "-freflection" }
> > > +
> > > +#include <meta>
> > > +
> > > +void f() {}
> > > +struct s {
> > > + int get_z(this s) { return 4; }
> > > + static int get_y() { return 4; }
> > > + int get_x() { return 3; }
> > > + int xx;
> > > + static int xxx;
> > > +};
> > > +
> > > +int s::xxx = 5;
> > > +
> > > +template<std::meta::info refl_expr>
> > > +constexpr auto test() {
> > > + return &[:refl_expr:];
> > > +}
> > > +
> > > +int main() {
> > > + static_assert(test<^^s::get_z>() == &s::get_z);
> > > + static_assert(test<^^s::get_y>() == &s::get_y);
> > > + static_assert(test<^^s::get_x>() == &s::get_x);
> > > + static_assert(test<^^s::xx>() == &s::xx);
> > > + static_assert(test<^^s::xxx>() == &s::xxx);
> > > + static_assert(test<^^f>() == &f);
> > > +}
> 
> 
> Marek

Here's a v2 of the patch, bootstrapped and tested on x86_64-pc-linux-gnu.
-- >8 --
In case of expressions like `&[:expr:]` where `expr` depends on a
template parameter, and the splice expression represents a `FIELD_DECL` or
a non-static member `FUNCTION_DECL`, that's exactly what we'd pass on.
However, `build_x_unary_op()` for these expressions is expecting an
`OFFSET_REF`. `OFFSET_REF` is also what gets passed to
`build_x_unary_op()` when templates are not involved.

There's also a difference between the template argument being a type and
using `members_of()` to get to the reflections of members (in which case
evaluating the `SPLICE_EXPR` returns a `FUNCTION_DECL` - `splice10.C`
test) and passing `^^T::member` as the template argument (in which case
evaluating the `SPLICE_EXPR` returns a `BASELINK` - `splice11.C`).

Signed-off-by: Boris Staletic <[email protected]>

        PR c++/123660
        PR c++/123661

gcc/cp/ChangeLog:

        * pt.cc (tsubst_splice_expr): Handle pointers to non-static members
        from splice expressions

gcc/testsuite/ChangeLog:

        * g++.dg/reflect/splice10.C: New test.
        * g++.dg/reflect/splice11.C: New test.
---
 gcc/cp/pt.cc                            | 14 ++++++++++++
 gcc/testsuite/g++.dg/reflect/splice10.C | 28 ++++++++++++++++++++++++
 gcc/testsuite/g++.dg/reflect/splice11.C | 29 +++++++++++++++++++++++++
 3 files changed, 71 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/reflect/splice10.C
 create mode 100644 gcc/testsuite/g++.dg/reflect/splice11.C

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 049bbf07e0..0c0076c9a4 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -16755,6 +16755,20 @@ tsubst_splice_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
   op = splice (op);
   if (op == error_mark_node)
     return error_mark_node;
+
+  if (SPLICE_EXPR_ADDRESS_P (t) && !TREE_STATIC (TREE_TYPE (op)))
+    {
+      if (TREE_CODE (op) == BASELINK
+         && TREE_CODE (TREE_TYPE (op)) == METHOD_TYPE)
+       op = build_offset_ref (BINFO_TYPE (BASELINK_ACCESS_BINFO (op)), op,
+                              /*address_p=*/true, complain);
+      else if (TREE_CODE (op) == FIELD_DECL
+              || (TREE_CODE (op) == FUNCTION_DECL
+                  && TREE_CODE (TREE_TYPE (op)) == METHOD_TYPE))
+       op = build_offset_ref (DECL_CONTEXT (op), op,
+                              /*address_p=*/true, complain);
+    }
+
   if (dependent_splice_p (op))
     {
       if (SPLICE_EXPR_EXPRESSION_P (t))
diff --git a/gcc/testsuite/g++.dg/reflect/splice10.C 
b/gcc/testsuite/g++.dg/reflect/splice10.C
new file mode 100644
index 0000000000..2f335ea8fc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/splice10.C
@@ -0,0 +1,28 @@
+// PR c++/123660
+// PR c++/123661
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+struct s {
+  int get_z(this s) { return 4; }
+  static int get_y() { return 4; }
+  int get_x() { return 3; }
+  int xx;
+  static int xxx;
+};
+
+int s::xxx = 5;
+
+template<typename T, decltype(0uz) I>
+constexpr auto test() {
+  constexpr auto ctx = std::meta::access_context::current();
+  return &[:members_of(^^T, ctx)[I]:];
+}
+
+static_assert(test<s, 0uz>() == &s::get_z);
+static_assert(test<s, 1uz>() == &s::get_y);
+static_assert(test<s, 2uz>() == &s::get_x);
+static_assert(test<s, 3uz>() == &s::xx);
+static_assert(test<s, 4uz>() == &s::xxx);
diff --git a/gcc/testsuite/g++.dg/reflect/splice11.C 
b/gcc/testsuite/g++.dg/reflect/splice11.C
new file mode 100644
index 0000000000..b62fdcc5ee
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/splice11.C
@@ -0,0 +1,29 @@
+// PR c++/123660
+// PR c++/123661
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+void f() {}
+struct s {
+  int get_z(this s) { return 4; }
+  static int get_y() { return 4; }
+  int get_x() { return 3; }
+  int xx;
+  static int xxx;
+};
+
+int s::xxx = 5;
+
+template<std::meta::info refl_expr>
+constexpr auto test() {
+  return &[:refl_expr:];
+}
+
+static_assert(test<^^s::get_z>() == &s::get_z);
+static_assert(test<^^s::get_y>() == &s::get_y);
+static_assert(test<^^s::get_x>() == &s::get_x);
+static_assert(test<^^s::xx>() == &s::xx);
+static_assert(test<^^s::xxx>() == &s::xxx);
+static_assert(test<^^f>() == &f);
-- 
2.51.1

Reply via email to