On 2/10/26 5:00 PM, Boris Staletic wrote:
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)))

TREE_STATIC on a type isn't meaningful, the checks below are enough.

+    {
+      if (TREE_CODE (op) == BASELINK
+         && TREE_CODE (TREE_TYPE (op)) == METHOD_TYPE)

It shouldn't be necessary to check for METHOD_TYPE; any class member is suitable for passing to build_offset_ref. If the argument is static, it'll return it unchanged rather than actually build an OFFSET_REF.

+       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);

Reply via email to