Subject: Re: Make copyObject work in C++

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Sun, 25 Jan 2026 13:42:51 +0100

On Tue Jan 20, 2026 at 5:28 PM CET, Peter Eisentraut wrote:
> I have split your first patch further.  For a start, I left out the 
> PG_MODULE_MAGIC*-related changes and disabled the module under MSVC. 
> This has been committed.  I plan to let the buildfarm run with it for a 
> day or two and then add in the basic MSVC support.

To hopefully make your life a bit easier. Here's a rebased version that
enables the MSVC support again, with an updated commit message.
From 63397ed786c0feb42f9a29989f8c53fe0c9c07ec Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 2 Jan 2026 22:31:05 +0100
Subject: [PATCH v7 1/5] Support building C++ extensions with MSVC

In 476b35d4e31 a C++ test extension module was added. This test was
disabled for MSVC, because it failed to compile it in any C++ version
lower than C++20. The reason for that is our usage of designated
initializers in PG_MODULE_MAGIC_DATA (which was done in 9324c8c580).
Designated initialzers are only part of C++ from the C++20 standard
(although clang and gcc implemented support way earlier).

This reverts to using positional initializers in PG_MODULE_MAGIC_DATA so
that its possible to write C++ extensions in standard C++11. Sadly that
means that using designated initializers in C++20 is still not allowed
in PG_MODULE_MAGIC_EXT because mixing designated an positional
initializers is a C only feature. This restriction for C++ extensions is
now documented and tested.
---
 doc/src/sgml/xfunc.sgml                              |  6 ++++++
 meson.build                                          |  4 ++++
 src/include/fmgr.h                                   | 12 +++++++-----
 .../test_cplusplusext/expected/test_cplusplusext.out |  7 +++++++
 src/test/modules/test_cplusplusext/meson.build       |  5 -----
 .../test_cplusplusext/sql/test_cplusplusext.sql      |  3 +++
 .../modules/test_cplusplusext/test_cplusplusext.cpp  |  2 +-
 7 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 70e815b8a2c..143f87a253a 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1973,6 +1973,12 @@ PG_MODULE_MAGIC_EXT(
     .name = "my_module_name",
     .version = "1.2.3"
 );
+</programlisting>
+
+    In C++ code, use positional arguments instead of designated initializers:
+
+<programlisting>
+PG_MODULE_MAGIC_EXT("my_module_name", "1.2.3");
 </programlisting>
 
     Subsequently the name and version can be examined via
diff --git a/meson.build b/meson.build
index df907b62da3..056412890b5 100644
--- a/meson.build
+++ b/meson.build
@@ -2238,6 +2238,10 @@ if cc.get_id() == 'msvc'
     '/w24777', # 'function' : format string 'string' requires an argument of type 'type1', but variadic argument number has type 'type2' [like -Wformat]
   ]
 
+  cxxflags_warn += [
+    '/wd4200', # nonstandard extension used: zero-sized array in struct/union
+  ]
+
   cppflags += [
     '/DWIN32',
     '/DWINDOWS',
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index eabbc78b280..1ab8749cee1 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -497,13 +497,15 @@ typedef struct
 }
 
 /*
- * Macro to fill a magic block.  If any arguments are given, they should
- * be field initializers.
+ * Macro to fill a magic block.  If any arguments are given, they should be
+ * field initializers. These can be designated initialzers, or non-designated
+ * initializers. If they're non-designated, they are applied after the ABI
+ * fields.
  */
 #define PG_MODULE_MAGIC_DATA(...) \
 { \
-	.len = sizeof(Pg_magic_struct), \
-	.abi_fields = PG_MODULE_ABI_DATA, \
+	sizeof(Pg_magic_struct), \
+	PG_MODULE_ABI_DATA, \
 	__VA_ARGS__ \
 }
 
@@ -524,7 +526,7 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
 const Pg_magic_struct * \
 PG_MAGIC_FUNCTION_NAME(void) \
 { \
-	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(.name = NULL); \
+	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(); \
 	return &Pg_magic_data; \
 } \
 extern int no_such_variable
diff --git a/src/test/modules/test_cplusplusext/expected/test_cplusplusext.out b/src/test/modules/test_cplusplusext/expected/test_cplusplusext.out
index ab0b04b5c5e..25600cfd1b4 100644
--- a/src/test/modules/test_cplusplusext/expected/test_cplusplusext.out
+++ b/src/test/modules/test_cplusplusext/expected/test_cplusplusext.out
@@ -5,3 +5,10 @@ SELECT test_cplusplus_add(1, 2);
                   3
 (1 row)
 
+SELECT module_name, version FROM pg_get_loaded_modules()
+  WHERE module_name = 'test_cplusplusext';
+    module_name    | version 
+-------------------+---------
+ test_cplusplusext | 1.2
+(1 row)
+
diff --git a/src/test/modules/test_cplusplusext/meson.build b/src/test/modules/test_cplusplusext/meson.build
index d13210ca593..24a9cf16dca 100644
--- a/src/test/modules/test_cplusplusext/meson.build
+++ b/src/test/modules/test_cplusplusext/meson.build
@@ -4,11 +4,6 @@ if not have_cxx
   subdir_done()
 endif
 
-# Currently not supported, to be fixed.
-if cc.get_id() == 'msvc'
-  subdir_done()
-endif
-
 test_cplusplusext_sources = files(
   'test_cplusplusext.cpp',
 )
diff --git a/src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql b/src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql
index a41682417ae..693910ba637 100644
--- a/src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql
+++ b/src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql
@@ -1,3 +1,6 @@
 CREATE EXTENSION test_cplusplusext;
 
 SELECT test_cplusplus_add(1, 2);
+
+SELECT module_name, version FROM pg_get_loaded_modules()
+  WHERE module_name = 'test_cplusplusext';
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index 435937c00d2..7108e5b1cc5 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -18,7 +18,7 @@ extern "C" {
 #include "postgres.h"
 #include "fmgr.h"
 
-PG_MODULE_MAGIC;
+PG_MODULE_MAGIC_EXT("test_cplusplusext", "1.2");
 
 PG_FUNCTION_INFO_V1(test_cplusplus_add);
 }

base-commit: 9b9eaf08ab2dc22c691b22e59f1574e0f1bcc822
-- 
2.52.0

From 2d7130aab09383c88ca8c98d666144df70d0f2da Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 5 Dec 2025 15:37:59 +0100
Subject: [PATCH v7 2/5] Support using copyObject in C++

Calling copyObject in C++ without GNU extensions fails with an error
like this:

error: use of undeclared identifier 'typeof'; did you mean 'typeid'

This is due to the C compiler supporting used to compile postgres
supporting typeof, but that function actually not being present in the
C++ compiler. This fixes that by defining pg_exprtype which maps to
typeof or decltype depending on the compiler. While pg_typeof would have
been a more natural name, that name is already taken in our codebase as
the implementation of the pg_typeof UDF.
---
 src/include/c.h                                     | 13 +++++++++++++
 src/include/nodes/nodes.h                           |  4 ++--
 .../modules/test_cplusplusext/test_cplusplusext.cpp |  6 ++++++
 3 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/src/include/c.h b/src/include/c.h
index c0be07a4566..08490641906 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -412,6 +412,19 @@
 #define unlikely(x) ((x) != 0)
 #endif
 
+/*
+ * pg_exprtype
+ *		Get the type of an expression at compile time.
+ *
+ * In C++ we use decltype since typeof is not standard C++, while in C we use
+ * typeof when available.
+ */
+#if defined(__cplusplus)
+#define pg_exprtype(x) decltype(x)
+#elif defined(HAVE_TYPEOF)
+#define pg_exprtype(x) typeof(x)
+#endif
+
 /*
  * CppAsString
  *		Convert the argument to a string, using the C preprocessor.
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b6ad28618ab..f5e17e670b7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -226,8 +226,8 @@ extern int16 *readAttrNumberCols(int numCols);
 extern void *copyObjectImpl(const void *from);
 
 /* cast result back to argument type, if supported by compiler */
-#ifdef HAVE_TYPEOF
-#define copyObject(obj) ((typeof(obj)) copyObjectImpl(obj))
+#ifdef pg_exprtype
+#define copyObject(obj) ((pg_exprtype(obj)) copyObjectImpl(obj))
 #else
 #define copyObject(obj) copyObjectImpl(obj)
 #endif
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index 7108e5b1cc5..48741f27949 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -17,6 +17,7 @@
 extern "C" {
 #include "postgres.h"
 #include "fmgr.h"
+#include "nodes/parsenodes.h"
 
 PG_MODULE_MAGIC_EXT("test_cplusplusext", "1.2");
 
@@ -32,6 +33,11 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 {
 	int32		a = PG_GETARG_INT32(0);
 	int32		b = PG_GETARG_INT32(1);
+	RangeTblRef *node = makeNode(RangeTblRef);
+	RangeTblRef *copy = copyObject(node);
+
+	pfree(copy);
+	pfree(node);
 
 	PG_RETURN_INT32(a + b);
 }
-- 
2.52.0

From 4e7cf0efe82b5f73048c488ffb1fc6619d6dbb65 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 8 Dec 2025 08:13:51 +0100
Subject: [PATCH v7 3/5] Use pg_exprtype instead of typeof

The previous commit introduced pg_exprtype. This starts using that in a
few more places.
---
 src/include/c.h            | 8 ++++----
 src/include/utils/relptr.h | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/include/c.h b/src/include/c.h
index 08490641906..9c54a67506d 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -978,10 +978,10 @@ pg_noreturn extern void ExceptionalCondition(const char *conditionName,
  */
 #ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P
 #define AssertVariableIsOfType(varname, typename) \
-	StaticAssertStmt(__builtin_types_compatible_p(__typeof__(varname), typename), \
+	StaticAssertStmt(__builtin_types_compatible_p(pg_exprtype(varname), typename), \
 	CppAsString(varname) " does not have type " CppAsString(typename))
 #define AssertVariableIsOfTypeMacro(varname, typename) \
-	(StaticAssertExpr(__builtin_types_compatible_p(__typeof__(varname), typename), \
+	(StaticAssertExpr(__builtin_types_compatible_p(pg_exprtype(varname), typename), \
 	 CppAsString(varname) " does not have type " CppAsString(typename)))
 #else							/* !HAVE__BUILTIN_TYPES_COMPATIBLE_P */
 #define AssertVariableIsOfType(varname, typename) \
@@ -1229,11 +1229,11 @@ typedef struct PGAlignedXLogBlock
 #define unvolatize(underlying_type, expr) const_cast<underlying_type>(expr)
 #elif defined(HAVE__BUILTIN_TYPES_COMPATIBLE_P)
 #define unconstify(underlying_type, expr) \
-	(StaticAssertExpr(__builtin_types_compatible_p(__typeof(expr), const underlying_type), \
+	(StaticAssertExpr(__builtin_types_compatible_p(pg_exprtype(expr), const underlying_type), \
 					  "wrong cast"), \
 	 (underlying_type) (expr))
 #define unvolatize(underlying_type, expr) \
-	(StaticAssertExpr(__builtin_types_compatible_p(__typeof(expr), volatile underlying_type), \
+	(StaticAssertExpr(__builtin_types_compatible_p(pg_exprtype(expr), volatile underlying_type), \
 					  "wrong cast"), \
 	 (underlying_type) (expr))
 #else
diff --git a/src/include/utils/relptr.h b/src/include/utils/relptr.h
index aeb17fa24a5..3e03d34d9ad 100644
--- a/src/include/utils/relptr.h
+++ b/src/include/utils/relptr.h
@@ -38,10 +38,10 @@
 #define relptr_declare(type, relptrtype) \
 	typedef relptr(type) relptrtype
 
-#ifdef HAVE_TYPEOF
+#ifdef pg_exprtype
 #define relptr_access(base, rp) \
 	(AssertVariableIsOfTypeMacro(base, char *), \
-	 (typeof((rp).relptr_type)) ((rp).relptr_off == 0 ? NULL : \
+	 (pg_exprtype((rp).relptr_type)) ((rp).relptr_off == 0 ? NULL : \
 		(base) + (rp).relptr_off - 1))
 #else
 #define relptr_access(base, rp) \
@@ -68,10 +68,10 @@ relptr_store_eval(char *base, char *val)
 	}
 }
 
-#ifdef HAVE_TYPEOF
+#ifdef pg_exprtype
 #define relptr_store(base, rp, val) \
 	(AssertVariableIsOfTypeMacro(base, char *), \
-	 AssertVariableIsOfTypeMacro(val, typeof((rp).relptr_type)), \
+	 AssertVariableIsOfTypeMacro(val, pg_exprtype((rp).relptr_type)), \
 	 (rp).relptr_off = relptr_store_eval((base), (char *) (val)))
 #else
 #define relptr_store(base, rp, val) \
-- 
2.52.0

From bd3384dd449d68818573599201f66e7f7048174e Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Sat, 17 Jan 2026 14:51:36 +0100
Subject: [PATCH v7 4/5] Support using StaticAssert macros in C++

StaticAssertExpr didn't work in MSVC C++. This adds a dedicated C++
definition which uses an inline lamdbda, that calls only the static
assert. Since this results in empty lambda, the actual call will be
removed by any compiler that does some optimization.
---
 src/include/c.h                                          | 8 ++++++--
 src/test/modules/test_cplusplusext/test_cplusplusext.cpp | 5 +++++
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/include/c.h b/src/include/c.h
index 9c54a67506d..0e4befb9e85 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -956,13 +956,17 @@ pg_noreturn extern void ExceptionalCondition(const char *conditionName,
 	static_assert(condition, errmessage)
 #define StaticAssertStmt(condition, errmessage) \
 	do { static_assert(condition, errmessage); } while(0)
-#ifdef HAVE_STATEMENT_EXPRESSIONS
+#ifdef __cplusplus
+/* C++11 lambdas provide a convenient way to use static_assert as an expression */
+#define StaticAssertExpr(condition, errmessage) \
+	((void) ([](){ static_assert(condition, errmessage); }(), 0))
+#elif defined(HAVE_STATEMENT_EXPRESSIONS)
 #define StaticAssertExpr(condition, errmessage) \
 	((void) ({ static_assert(condition, errmessage); true; }))
 #else
 #define StaticAssertExpr(condition, errmessage) \
 	((void) sizeof(struct { int static_assert_failure : (condition) ? 1 : -1; }))
-#endif							/* HAVE_STATEMENT_EXPRESSIONS */
+#endif

 /*
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index 48741f27949..bb9c310504e 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -24,6 +24,8 @@ PG_MODULE_MAGIC_EXT("test_cplusplusext", "1.2");
 PG_FUNCTION_INFO_V1(test_cplusplus_add);
 }
 
+StaticAssertDecl(sizeof(int32) == 4, "int32 should be 4 bytes");
+
 /*
  * Simple function that returns the sum of two integers.  This verifies that
  * C++ extension modules can be loaded and called correctly at runtime.
@@ -36,6 +38,9 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 	RangeTblRef *node = makeNode(RangeTblRef);
 	RangeTblRef *copy = copyObject(node);
 
+	StaticAssertStmt(sizeof(int32) == 4, "int32 should be 4 bytes");
+	(void) StaticAssertExpr(sizeof(int64) == 8, "int64 should be 8 bytes");
+
 	pfree(copy);
 	pfree(node);
 
-- 
2.52.0

From dabaf5ef094b06a694f956f7b6749ade11c9b846 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Sat, 17 Jan 2026 14:55:28 +0100
Subject: [PATCH v7 5/5] Support using list_make macros in C++

The list_make_xxx_cell macros were using designated initializers and
these are only officially available in C++20. GCC and Clang allow them
in earlier C++ versions too, but MSVC is strict about it. Since we want
to support C++11 this changes these macros to use inline functions
instead, which work across both C and C++.
---
 src/include/nodes/pg_list.h                   | 43 ++++++++++++++++---
 .../test_cplusplusext/test_cplusplusext.cpp   | 14 ++++++
 2 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index ae80975548f..ba43d36bc89 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -202,12 +202,45 @@ list_length(const List *l)
 #define llast_node(type,l)		castNode(type, llast(l))
 
 /*
- * Convenience macros for building fixed-length lists
+ * Convenience functions for building fixed-length lists. These cannot be
+ * macros with designated initializers, because we want these functions to be
+ * usable from C++ versions below C++20.
  */
-#define list_make_ptr_cell(v)	((ListCell) {.ptr_value = (v)})
-#define list_make_int_cell(v)	((ListCell) {.int_value = (v)})
-#define list_make_oid_cell(v)	((ListCell) {.oid_value = (v)})
-#define list_make_xid_cell(v)	((ListCell) {.xid_value = (v)})
+static inline ListCell
+list_make_ptr_cell(void *v)
+{
+	ListCell	c;
+
+	c.ptr_value = v;
+	return c;
+}
+
+static inline ListCell
+list_make_int_cell(int v)
+{
+	ListCell	c;
+
+	c.int_value = v;
+	return c;
+}
+
+static inline ListCell
+list_make_oid_cell(Oid v)
+{
+	ListCell	c;
+
+	c.oid_value = v;
+	return c;
+}
+
+static inline ListCell
+list_make_xid_cell(TransactionId v)
+{
+	ListCell	c;
+
+	c.xid_value = v;
+	return c;
+}
 
 #define list_make1(x1) \
 	list_make1_impl(T_List, list_make_ptr_cell(x1))
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index bb9c310504e..2bdf2c3d057 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -17,6 +17,7 @@
 extern "C" {
 #include "postgres.h"
 #include "fmgr.h"
+#include "nodes/pg_list.h"
 #include "nodes/parsenodes.h"
 
 PG_MODULE_MAGIC_EXT("test_cplusplusext", "1.2");
@@ -41,6 +42,19 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 	StaticAssertStmt(sizeof(int32) == 4, "int32 should be 4 bytes");
 	(void) StaticAssertExpr(sizeof(int64) == 8, "int64 should be 8 bytes");
 
+	List	   *list = list_make1(node);
+
+	foreach_ptr(RangeTblRef, rtr, list)
+	{
+		(void) rtr;
+	}
+
+	foreach_node(RangeTblRef, rtr, list)
+	{
+		(void) rtr;
+	}
+
+	list_free(list);
 	pfree(copy);
 	pfree(node);
 
-- 
2.52.0

---

From: Andres Freund <[email-masked]>
Date: Sun, 25 Jan 2026 11:50:28 -0500

Hi,

> now documented and tested.

I'm pretty sceptical this is the right direction. We were going for designated
initializers for a reason, namely that we expect more arguments to be added
over time and perhaps eventually also to remove some. And this will just lead
to that being harder because we have to worry about C++ extensions.

But I'm also confused as to why it's needed - there's nomixing of designated
and non-designated initializers that I can see? If you use
PG_MODULE_MAGIC_EXT(.name = "whatnot"), it evaluates down to

extern __attribute__((visibility("default"))) const Pg_magic_struct *Pg_magic_func(void); const Pg_magic_struct * Pg_magic_func(void) { static const Pg_magic_struct Pg_magic_data = { .len = sizeof(Pg_magic_struct), .abi_fields = { 190000 / 100, 100, 32, 64, true, "PostgreSQL", }, .name="whatnot"}; return &Pg_magic_data; } extern int no_such_variable;

And indeed, contra to what you reported upthread, I can't get clang to report
a warning about that.  I do obviously see warnings about the wrong order if I
pass the arguments in the wrong order, but that's a lot less problematic. And
omitted args don't trigger warnings, as you noted.

Greetings,

Andres Freund

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Sun, 25 Jan 2026 18:52:37 +0100

On Sun Jan 25, 2026 at 5:50 PM CET, Andres Freund wrote:
> I'm pretty sceptical this is the right direction. 

The only other option I can think of is not supporting C++ extension on
MSVC unless they are compiled with C++20 (or later). I don't
particularly care about Windows C++ extensions myself, so I personally
would be fine with that choice. It seems a bit harsh, though.

> We were going for designated

Adding new arguments (aka fields) should cause no problems. Assuming
we'd add them at the end of the Pg_magic_struct definition. Removing
ones seems like even for C you'd need different PG_MODULE_MAGIC_EXT
invocations depending on PG_VERSION_NUM. I don't see how using
positional args would make that harder.

> omitted args don't trigger warnings, as you noted.

It sounds like I wasn't clear enough what the problem was. The main
problem currently is that MSVC C++ fails to compile a cpp file
containing PG_MODULE_MAGIC (or PG_MODULE_MAGIC_EXT) unless you use /std:c++20.

Afaict it would have been possible to do so before 9324c8c580 (aka
anything before PG18), but since that commit you need /std:c++20. The
fact that we haven't heard anyone complain so far, might be an indication
that not many people (or maybe anyone) is using C++ extensions on Windows.

The only way to make PG_MODULE_MAGIC work on MSVC pre-C++20 is to
partially revert 9324c8c580, and use positional arguments in the
definition of PG_MODULE_MAGIC_DATA again. Like I've done in v7-0001.

For C extensions v7-0001 has no downsides. However, after applying
v7-0001, C++ extensions that use PG_MODULE_MAGIC_EXT with designated
parameters will get the following warning on at least clang 18 on my
machine:

meson setup --prefix ~/.pgenv/pgsql-master --debug build --reconfigure -Dc_args='-fno-omit-frame-pointer' -Dcassert=true
meson test -C build --suite setup --suite test_cplusplusext

[2202/2268] Compiling C++ object src/test/modules/test_cplusplusext/test_cplusplusext.so.p/test_cplusplusext.cpp.o
../src/test/modules/test_cplusplusext/test_cplusplusext.cpp:23:21: warning: mixture of designated and non-designated initializers in the same initializer list is a C99 extension [-Wc99-designator]
   23 | PG_MODULE_MAGIC_EXT(.name="test_cplusplusext",.version= "1.2");
      |                     ^~~~~~~~~~~~~~~~~~~~~~~~~
../src/include/fmgr.h:548:24: note: expanded from macro 'PG_MODULE_MAGIC_EXT'
  548 |                 PG_MODULE_MAGIC_DATA(__VA_ARGS__); \
      |                                      ^~~~~~~~~~~
../src/include/fmgr.h:509:2: note: expanded from macro 'PG_MODULE_MAGIC_DATA'
  509 |         __VA_ARGS__ \
      |         ^~~~~~~~~~~
../src/test/modules/test_cplusplusext/test_cplusplusext.cpp:23:1: note: first non-designated initializer is here
   23 | PG_MODULE_MAGIC_EXT(.name="test_cplusplusext",.version= "1.2");
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../src/include/fmgr.h:548:3: note: expanded from macro 'PG_MODULE_MAGIC_EXT'
  548 |                 PG_MODULE_MAGIC_DATA(__VA_ARGS__); \
      |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../src/include/fmgr.h:507:2: note: expanded from macro 'PG_MODULE_MAGIC_DATA'
  507 |         sizeof(Pg_magic_struct), \
      |         ^~~~~~~~~~~~~~~~~~~~~~~

Note that you need to use meson test, not meson install, otherwise
test_cplusplusext.cpp won't get compiled.

So to be clear, yes right now there's no mixing of designated and
non-designated initializers. But after applying v7-0001 there would be
when you use something like this:
PG_MODULE_MAGIC_EXT(.name="test_cplusplusext",.version= "1.2");

And while the C standard allows mixing designated and non-designated
initializerss, the C++ standard (even C++20) does not.

---

From: Andres Freund <[email-masked]>
Date: Sun, 25 Jan 2026 15:06:12 -0500

Hi,

> positional args would make that harder.

Named args make that easier in two ways: First, only extensions using the
to-be-removed option will fail. Second, removal of options reliably generates
errors, rather than bogusly use one field for another, just because the types
are compatible.

Greetings,

Andres Freund

---

From: Peter Eisentraut <[email-masked]>
Date: Mon, 26 Jan 2026 11:29:42 +0100

> would be fine with that choice. It seems a bit harsh, though.

Maybe it would be enough to only support PG_MODULE_MAGIC (without 
arguments) in C++ for now.

That would still require removing the named initializers inside 
PG_MODULE_MAGIC_DATA(), but that's only an internal change, and it would 
not affect C-language users of PG_MODULE_MAGIC_EXT, and we wouldn't lock 
ourselves into a particular style in C++.

Then, maybe in a couple of years, when C++20 is more widely available, 
we could enable PG_MODULE_MAGIC_EXT for C++, or design an alternative 
construct for C++.

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 26 Jan 2026 11:43:54 +0100

> arguments) in C++ for now.

You mean by explicitly ifdefing out PG_MODULE_MAGIC_EXT to make it
unusable in C++? Or just not adding the additional documentation that
I added in my patch?
I'm currently using PG_MODULE_MAGIC_EXT in the C++ extension that I
maintain, and I'd prefer to continue doing so. Especially because it
(currently) doesn't have to work on MSVC for my purposes.

---

From: Bryan Green <[email-masked]>
Date: Wed, 4 Feb 2026 09:54:37 -0600

On 1/25/2026 6:42 AM, Jelte Fennema-Nio wrote:
>> This has been committed.  I plan to let the buildfarm run with it for
>> a day or two and then add in the basic MSVC support.
> 
This causes the build to break on VS 2022.  Versions prior do not have
support for __typeof_unqual__.  This would have led to
HAVE_TYPEOF_UNQUAL being not defined on the buildfarm causing the
fallback to copyObjectImpl() for VS 2019.

When you build on VS 2022 you will get an error:
../src/backend/commands/event_trigger.c(1901): error C2100: you cannot
dereference an operand of type 'void'

VS 2022 (MSVC) does not handle the void * dereference the way gcc/clang
does (thanks to GNU extensions, I believe).  It exposes
__typeof_unqual__ even it C11, but enforces strict C semantics on the
void * dereference.  To work on this platform the call
copyObject(lfirst(cell)) would need to have a cast to the correct
concrete type: copyObject((Node *)lfirst(cell)).

I was about to update to VS 2026, but now I think I should have an
instance of VS from 2019, 2022, and 2026.

-- 
Bryan Green
EDB: https://www.enterprisedb.com

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Fri, 13 Feb 2026 11:28:20 +0100

> errors, rather than bogusly use one field for another, just because the types

After discussing the topic in-person with Peter at FOSDEM. We agreed
that the best road forward was to not bother with MSVC for now. No-one
has actually expressed an interest in being able to build C++ extension
using MSVC, and the effort to support it is both non-trivial and not
without downsides to the rest of the codebase. We can always come back
to this later, possibly requiring C++20 on MSVC.

So I've removed that patch and now this patchset its goal is to improve
compatibiltity with the C++ flavor of GCC and Clang.

Patch 1 and 2 add some more macro calls to our test C++ extension. These
macros already work in GCC and Clang, this is purely to test for future
regressinos.

Patch 3 makes copyObject work when using GCC or Clang with -std=c++11 by
introducing pg_exprtype.

Patch 4 starts using pg_exprtype in more places.

I'm also working on some patches to support
StaticAssertVariableIsOfType, but I've run into some ICE compiler errors
of MSVC 2019.
From 8a5bbbd0e73edafc340c938ec6e234384dbe672a Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 13 Feb 2026 09:14:47 +0100
Subject: [PATCH v8 1/6] Test List macros in C++ extensions

All of these macros already work in C++ with Clang and GCC (the only
compilers we're currently testing C++ extension support for). This adds
a regression test for them in our test C++ extension, so we can safely
change their implementation without accidentally breaking C++. Some of
the List macros didn't work in C++ in the past (d5ca15ee5), this would
have caught that.
---
 .../test_cplusplusext/test_cplusplusext.cpp     | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index 435937c00d2..f1a2ab7f2bf 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -17,6 +17,8 @@
 extern "C" {
 #include "postgres.h"
 #include "fmgr.h"
+#include "nodes/pg_list.h"
+#include "nodes/primnodes.h"
 
 PG_MODULE_MAGIC;
 
@@ -32,6 +34,21 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 {
 
 	PG_RETURN_INT32(a + b);
 }

base-commit: d7edcec35c7c28edb3bf283dfe9c892b042ca158
-- 
2.52.0

From 8f9d2a63bcdf30a445cb24c5453f34d8f54a4b29 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Sat, 17 Jan 2026 14:51:36 +0100
Subject: [PATCH v8 2/6] Test most StaticAssert macros in C++ extensions

Most of the StaticAssert macros already worked in C++ with Clang and GCC
(the only compilers we're currently testing C++ extension support for).
This adds a regression test for them in our test C++ extension, so we
can safely change their implementation without accidentally breaking
C++.

The only macros that StaticAssert macros that don't work yet are the
StaticAssertVariableIsOfType and StaticAssertVariableIsOfTypeMacro.
These will be added in a follow on commit.
---
 src/test/modules/test_cplusplusext/test_cplusplusext.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index f1a2ab7f2bf..8c2eabcca43 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -25,6 +25,8 @@ PG_MODULE_MAGIC;
 PG_FUNCTION_INFO_V1(test_cplusplus_add);
 }
 
+StaticAssertDecl(sizeof(int32) == 4, "int32 should be 4 bytes");
+
 /*
  * Simple function that returns the sum of two integers.  This verifies that
  * C++ extension modules can be loaded and called correctly at runtime.
@@ -47,6 +49,9 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 		(void) rtr;
 	}
 
 	list_free(list);
 	pfree(node);
 
-- 
2.52.0

From cb57b403ee7819f7512a5e5edcd4ce1cb876ee46 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 5 Dec 2025 15:37:59 +0100
Subject: [PATCH v8 3/6] Support using copyObject in C++

Calling copyObject in C++ without GNU extensions (e.g. then using
-std=c++11 instead of -std=gnu++11) fails with an error like this:

This is due to the C compiler supporting used to compile postgres
supporting typeof, but that function actually not being present in the
C++ compiler. This fixes that by defining pg_exprtype which maps to
typeof or decltype depending on whether it's a C or C++ compiler. While
pg_typeof would have been a more natural name, that name is already
taken in our codebase as the implementation of the pg_typeof UDF.
---
 src/include/c.h                                     | 13 +++++++++++++
 src/include/nodes/nodes.h                           |  4 ++--
 .../modules/test_cplusplusext/test_cplusplusext.cpp |  2 ++
 3 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/src/include/c.h b/src/include/c.h
index 3fc09ec1e4a..9f418e432ea 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -412,6 +412,19 @@
 #define unlikely(x) ((x) != 0)
 #endif
 
+/*
+ * pg_exprtype
index 59a7df31aba..a03c85f1b01 100644
 
 /* cast result back to argument type, if supported by compiler */
-#ifdef HAVE_TYPEOF
-#define copyObject(obj) ((typeof(obj)) copyObjectImpl(obj))
+#ifdef pg_exprtype
+#define copyObject(obj) ((pg_exprtype(obj)) copyObjectImpl(obj))
 #else
 #define copyObject(obj) copyObjectImpl(obj)
 #endif
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index 8c2eabcca43..5e6c8f85f6f 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -37,6 +37,7 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 	int32		a = PG_GETARG_INT32(0);
 	int32		b = PG_GETARG_INT32(1);
 	RangeTblRef *node = makeNode(RangeTblRef);
+	RangeTblRef *copy = copyObject(node);
 	List	   *list = list_make1(node);
 
 	foreach_ptr(RangeTblRef, rtr, list)
@@ -54,6 +55,7 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 
 	list_free(list);
 	pfree(node);
+	pfree(copy);
 
 	PG_RETURN_INT32(a + b);
 }
-- 
2.52.0

From d2986a90548f5dce7fbd6d0e09d7d881f15e3e53 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 8 Dec 2025 08:13:51 +0100
Subject: [PATCH v8 4/6] Use pg_exprtype instead of typeof

diff --git a/src/include/c.h b/src/include/c.h
index 9f418e432ea..b448a4a8775 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -978,10 +978,10 @@ pg_noreturn extern void ExceptionalCondition(const char *conditionName,
  */
 #ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P
 #define StaticAssertVariableIsOfType(varname, typename) \
-	StaticAssertDecl(__builtin_types_compatible_p(__typeof__(varname), typename), \
+	StaticAssertDecl(__builtin_types_compatible_p(pg_exprtype(varname), typename), \
 	CppAsString(varname) " does not have type " CppAsString(typename))
 #define StaticAssertVariableIsOfTypeMacro(varname, typename) \
-	(StaticAssertExpr(__builtin_types_compatible_p(__typeof__(varname), typename), \
+	(StaticAssertExpr(__builtin_types_compatible_p(pg_exprtype(varname), typename), \
 	 CppAsString(varname) " does not have type " CppAsString(typename)))
 #else							/* !HAVE__BUILTIN_TYPES_COMPATIBLE_P */
 #define StaticAssertVariableIsOfType(varname, typename) \
@@ -1245,11 +1245,11 @@ typedef struct PGAlignedXLogBlock PGAlignedXLogBlock;
 #define unvolatize(underlying_type, expr) const_cast<underlying_type>(expr)
 #elif defined(HAVE__BUILTIN_TYPES_COMPATIBLE_P)
 #define unconstify(underlying_type, expr) \
-	(StaticAssertExpr(__builtin_types_compatible_p(__typeof(expr), const underlying_type), \
+	(StaticAssertExpr(__builtin_types_compatible_p(pg_exprtype(expr), const underlying_type), \
 					  "wrong cast"), \
 	 (underlying_type) (expr))
 #define unvolatize(underlying_type, expr) \
-	(StaticAssertExpr(__builtin_types_compatible_p(__typeof(expr), volatile underlying_type), \
+	(StaticAssertExpr(__builtin_types_compatible_p(pg_exprtype(expr), volatile underlying_type), \
 					  "wrong cast"), \
 	 (underlying_type) (expr))
 #else
diff --git a/src/include/utils/relptr.h b/src/include/utils/relptr.h
index 94975f2f237..2df6c673017 100644
--- a/src/include/utils/relptr.h
+++ b/src/include/utils/relptr.h
@@ -38,10 +38,10 @@
 #define relptr_declare(type, relptrtype) \
 	typedef relptr(type) relptrtype
 
-#ifdef HAVE_TYPEOF
+#ifdef pg_exprtype
 	(StaticAssertVariableIsOfTypeMacro(base, char *), \
 
-#ifdef HAVE_TYPEOF
+#ifdef pg_exprtype
 #define relptr_store(base, rp, val) \
 	(StaticAssertVariableIsOfTypeMacro(base, char *), \
-	 StaticAssertVariableIsOfTypeMacro(val, typeof((rp).relptr_type)), \
+	 StaticAssertVariableIsOfTypeMacro(val, pg_exprtype((rp).relptr_type)), \
 	 (rp).relptr_off = relptr_store_eval((base), (char *) (val)))
 #else
 #define relptr_store(base, rp, val) \
-- 
2.52.0

---

From: Peter Eisentraut <[email-masked]>
Date: Tue, 17 Feb 2026 10:24:42 +0100

> macros already work in GCC and Clang, this is purely to test for future

I have committed these two.  I'll give them some time on the buildfarm 
and then look at the rest.

---

From: Peter Eisentraut <[email-masked]>
Date: Thu, 19 Feb 2026 09:31:45 +0100

> Patch 4 starts using pg_exprtype in more places.

I found this paper

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2927.htm

which led to the addition of typeof into the C standard.

It contains a section 'Why not "decltype"?' that explains how decltype 
is different, but it also explains that if typeof were added to C++, 
then it would be

     std::remove_reference_t<decltype(T)>

What I suggest we should do here is:

1. Add a configure test that checks if the C++ compiler supports typeof.

2. If not, #define typeof to the above expression.

Then code can continue to use typeof unchanged.

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Fri, 20 Feb 2026 10:47:40 +0100

> Then code can continue to use typeof unchanged.

Makes total sense, I didn't realise decltype and typeof were not quite
the same thing. Attached is an updated patchset that does that.

It also includes a patch that improves unconstify and unvolatize by
using StaticAssertVariableIsOfTypeMacro instead of a custom version of
that assertion. (Like I said, in a future patch I intend to make
StaticAssertVariableIsOfTypeMacro work in C++ as well, but this seemed
like a good improvement anyway)
From 1ff9279f3acb207e23e8b20b23429283b81a94fb Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 5 Dec 2025 15:37:59 +0100
Subject: [PATCH v9 1/3] Support using copyObject in C++

This is due to the C compiler used to compile postgres supporting
typeof, but that function actually not being present in the C++
compiler. This fixes that by explicitely checking for typeof support in
C++ and then either use that, or define typeof ourselves as:

    std::remove_reference_t<decltype(x)>

According to the paper that led to adding typeof to the C standard,
that's the C++ equivalent of the C typeof:
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2927.htm#existing-decltype
---
 config/c-compiler.m4                          | 29 ++++++++++
 configure                                     | 54 +++++++++++++++++++
 configure.ac                                  |  1 +
 meson.build                                   | 25 +++++++++
 src/include/c.h                               | 21 ++++++++
 src/include/pg_config.h.in                    |  4 ++
 .../test_cplusplusext/test_cplusplusext.cpp   |  2 +
 7 files changed, 136 insertions(+)

diff --git a/config/c-compiler.m4 b/config/c-compiler.m4
index 1509dbfa2ab..5b3cbc7e8e8 100644
--- a/config/c-compiler.m4
+++ b/config/c-compiler.m4
@@ -193,6 +193,35 @@ fi])# PGAC_C_TYPEOF

+# PGAC_CXX_TYPEOF
+# ----------------
+# Check if the C++ compiler understands typeof or a variant.  Define
+# HAVE_CXX_TYPEOF if so, and define 'pg_cxx_typeof' to the actual key word.
+#
+AC_DEFUN([PGAC_CXX_TYPEOF],
+[AC_CACHE_CHECK(for C++ typeof, pgac_cv_cxx_typeof,
+[pgac_cv_cxx_typeof=no
+AC_LANG_PUSH(C++)
+for pgac_kw in typeof __typeof__; do
+  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
+[int x = 0;
+$pgac_kw(x) y;
+y = x;
+return y;])],
+[pgac_cv_cxx_typeof=$pgac_kw])
+  test "$pgac_cv_cxx_typeof" != no && break
+done
+AC_LANG_POP([])])
+if test "$pgac_cv_cxx_typeof" != no; then
+  AC_DEFINE(HAVE_CXX_TYPEOF, 1,
+            [Define to 1 if your C++ compiler understands `typeof' or something similar.])
+  if test "$pgac_cv_cxx_typeof" != typeof; then
+    AC_DEFINE_UNQUOTED(pg_cxx_typeof, $pgac_cv_cxx_typeof, [Define to how the C++ compiler spells `typeof'.])
+  fi
+fi])# PGAC_CXX_TYPEOF
+
+
+
 # PGAC_C_TYPES_COMPATIBLE
 # -----------------------
 # Check if the C compiler understands __builtin_types_compatible_p,
diff --git a/configure b/configure
index e1a08129974..3995a16fd1c 100755
--- a/configure
+++ b/configure
@@ -14968,6 +14968,60 @@ _ACEOF
 
   fi
 fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ typeof" >&5
+$as_echo_n "checking for C++ typeof... " >&6; }
+if ${pgac_cv_cxx_typeof+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  pgac_cv_cxx_typeof=no
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+for pgac_kw in typeof __typeof__; do
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+int x = 0;
+$pgac_kw(x) y;
+y = x;
+return y;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  pgac_cv_cxx_typeof=$pgac_kw
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  test "$pgac_cv_cxx_typeof" != no && break
+done
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_cxx_typeof" >&5
+$as_echo "$pgac_cv_cxx_typeof" >&6; }
+if test "$pgac_cv_cxx_typeof" != no; then
+
+$as_echo "#define HAVE_CXX_TYPEOF 1" >>confdefs.h
+
+  if test "$pgac_cv_cxx_typeof" != typeof; then
+
+cat >>confdefs.h <<_ACEOF
+#define pg_cxx_typeof $pgac_cv_cxx_typeof
+_ACEOF
+
+  fi
+fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __builtin_types_compatible_p" >&5
 $as_echo_n "checking for __builtin_types_compatible_p... " >&6; }
 if ${pgac_cv__types_compatible+:} false; then :
diff --git a/configure.ac b/configure.ac
index cc85c233c03..754dc50d6ba 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1716,6 +1716,7 @@ PGAC_PRINTF_ARCHETYPE
 PGAC_CXX_PRINTF_ARCHETYPE
 PGAC_C_STATEMENT_EXPRESSIONS
 PGAC_C_TYPEOF
+PGAC_CXX_TYPEOF
 PGAC_C_TYPES_COMPATIBLE
 PGAC_C_BUILTIN_CONSTANT_P
 PGAC_C_BUILTIN_OP_OVERFLOW
diff --git a/meson.build b/meson.build
index 055e96315d0..06ab917365e 100644
--- a/meson.build
+++ b/meson.build
@@ -2882,6 +2882,31 @@ int main(void)
   endif
 endforeach
 
+# Check if the C++ compiler understands typeof or a variant.
+if have_cxx
+  foreach kw : ['typeof', '__typeof__']
+    if cxx.compiles('''
+int main(void)
+{
+    int x = 0;
+    @0@(x) y;
+    y = x;
+    return y;
+}
+'''.format(kw),
+      name: 'C++ ' + kw,
+      args: test_c_args, include_directories: postgres_inc)
+
+      cdata.set('HAVE_CXX_TYPEOF', 1)
+      if kw != 'typeof'
+        cdata.set('pg_cxx_typeof', kw)
+      endif
+
+      break
+    endif
+  endforeach
+endif
+
 
 # MSVC doesn't cope well with defining restrict to __restrict, the
 # spelling it understands, because it conflicts with
diff --git a/src/include/c.h b/src/include/c.h
index 7ee4751992f..c3b3e9eea4e 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -424,6 +424,27 @@
 #define unlikely(x) ((x) != 0)
 #endif
 
+/*
+ * Provide typeof in C++ for C++ compilers that don't support typeof natively.
+ * It might be spelled __typeof__ instead of typeof, in which case
+ * pg_cxx_typeof provides that mapping. If neither is supported, we can use
+ * decltype, but to make it equivalent to C's typeof, we need to remove
+ * references from the result [1]. Also ensure HAVE_TYPEOF is set so that
+ * typeof-dependent code is always enabled in C++ mode.
+ *
+ * [1]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2927.htm#existing-decltype
+ */
+#if defined(__cplusplus)
+#ifdef pg_cxx_typeof
+#define typeof(x) pg_cxx_typeof(x)
+#elif !defined(HAVE_CXX_TYPEOF)
+#define typeof(x) std::remove_reference_t<decltype(x)>
+#endif
+#ifndef HAVE_TYPEOF
+#define HAVE_TYPEOF 1
+#endif
+#endif
+
 /*
  * CppAsString
  *		Convert the argument to a string, using the C preprocessor.
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 3824a5571bb..0059408569d 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -454,6 +454,10 @@
 /* Define to 1 if your compiler understands `typeof' or something similar. */
 #undef HAVE_TYPEOF
 
+/* Define to 1 if your C++ compiler understands `typeof' or something similar.
+   */
+#undef HAVE_CXX_TYPEOF
+
 /* Define to 1 if you have the <uchar.h> header file. */
 #undef HAVE_UCHAR_H

 
 	list_free(list);
 	pfree(node);
+	pfree(copy);
 
 	PG_RETURN_INT32(a + b);
 }

base-commit: ba401828c194c17031a6a2dedd97bc0defd80d4b
-- 
2.53.0

From c0537483fbdace6967df3aa1b76f1db6c66a5883 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 8 Dec 2025 08:13:51 +0100
Subject: [PATCH v9 2/3] Use typeof everywhere instead of compiler specific
 spellings

We define typeof ourselves as __typeof__ if it does not exist. So let's
actually use that for consistency. The meson/autoconf checks for
__builtin_types_compatible_p still use __typeof__ though, because there
we have not redefined it.
---
 src/include/c.h | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/include/c.h b/src/include/c.h
index c3b3e9eea4e..07e931a6c89 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -1008,10 +1008,10 @@ pg_noreturn extern void ExceptionalCondition(const char *conditionName,
  */
 #ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P
 #define StaticAssertVariableIsOfType(varname, typename) \
-	StaticAssertDecl(__builtin_types_compatible_p(__typeof__(varname), typename), \
+	StaticAssertDecl(__builtin_types_compatible_p(typeof(varname), typename), \
 	CppAsString(varname) " does not have type " CppAsString(typename))
 #define StaticAssertVariableIsOfTypeMacro(varname, typename) \
-	(StaticAssertExpr(__builtin_types_compatible_p(__typeof__(varname), typename), \
+	(StaticAssertExpr(__builtin_types_compatible_p(typeof(varname), typename), \
 	 CppAsString(varname) " does not have type " CppAsString(typename)))
 #else							/* !HAVE__BUILTIN_TYPES_COMPATIBLE_P */
 #define StaticAssertVariableIsOfType(varname, typename) \
@@ -1275,11 +1275,11 @@ typedef struct PGAlignedXLogBlock PGAlignedXLogBlock;
 #define unvolatize(underlying_type, expr) const_cast<underlying_type>(expr)
 #elif defined(HAVE__BUILTIN_TYPES_COMPATIBLE_P)
 #define unconstify(underlying_type, expr) \
-	(StaticAssertExpr(__builtin_types_compatible_p(__typeof(expr), const underlying_type), \
+	(StaticAssertExpr(__builtin_types_compatible_p(typeof(expr), const underlying_type), \
 					  "wrong cast"), \
 	 (underlying_type) (expr))
 #define unvolatize(underlying_type, expr) \
-	(StaticAssertExpr(__builtin_types_compatible_p(__typeof(expr), volatile underlying_type), \
+	(StaticAssertExpr(__builtin_types_compatible_p(typeof(expr), volatile underlying_type), \
 					  "wrong cast"), \
 	 (underlying_type) (expr))
 #else
-- 
2.53.0

From 37021f7b2cae580ca348c8600f9644f928b48232 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 19 Feb 2026 23:55:43 +0100
Subject: [PATCH v9 3/3] Make unconstify and unvolatize use
 StaticAssertVariableIsOfTypeMacro

The unconstify and unvolatize macros had an almost identical assertion
as was already defined in StaticAssertVariableIsOfTypeMacro, only it had
a less useful error message and didn't have a sizeof fallback.
---
 src/include/c.h | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/src/include/c.h b/src/include/c.h
index 07e931a6c89..5fa366914d8 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -1273,20 +1273,13 @@ typedef struct PGAlignedXLogBlock PGAlignedXLogBlock;
 #if defined(__cplusplus)
 #define unconstify(underlying_type, expr) const_cast<underlying_type>(expr)
 #define unvolatize(underlying_type, expr) const_cast<underlying_type>(expr)
-#elif defined(HAVE__BUILTIN_TYPES_COMPATIBLE_P)
+#else
 #define unconstify(underlying_type, expr) \
-	(StaticAssertExpr(__builtin_types_compatible_p(typeof(expr), const underlying_type), \
-					  "wrong cast"), \
+	(StaticAssertVariableIsOfTypeMacro(expr, const underlying_type), \
 	 (underlying_type) (expr))
 #define unvolatize(underlying_type, expr) \
-	(StaticAssertExpr(__builtin_types_compatible_p(typeof(expr), volatile underlying_type), \
-					  "wrong cast"), \
+	(StaticAssertVariableIsOfTypeMacro(expr, volatile underlying_type), \
 	 (underlying_type) (expr))
-#else
-#define unconstify(underlying_type, expr) \
-	((underlying_type) (expr))
-#define unvolatize(underlying_type, expr) \
-	((underlying_type) (expr))
 #endif
 
 /*
-- 
2.53.0

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Fri, 27 Feb 2026 17:40:21 +0100

> the same thing. Attached is an updated patchset that does that.

Same patchset as before, but now also including a C++ fallback for
__builtin_types_compatible_p.

From 077bb1a951c4eb6d61ba2e977c6b09d8d295831a Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 5 Dec 2025 15:37:59 +0100
Subject: [PATCH v10 1/4] Support using copyObject in C++

 
+# PGAC_CXX_TYPEOF
+# ----------------
index a285a6ec3d7..604f9e0100b 100755
--- a/configure
+++ b/configure
@@ -15078,6 +15078,60 @@ _ACEOF
 
   fi
 fi
index 476a76c7991..93ecf41447b 100644
@@ -1732,6 +1732,7 @@ PGAC_PRINTF_ARCHETYPE
index 5122706477d..9cc95f0be5f 100644
--- a/meson.build
+++ b/meson.build
@@ -2953,6 +2953,31 @@ int main(void)
   endif
 endforeach

index fb0ea1bc680..a4fee84398d 100644
 
+/*
index a0bd84376e7..a970577ae3e 100644
@@ -451,6 +451,10 @@

index eb129dd15d4..ea04a761184 100644

 	list_free(list);
 	pfree(node);
+	pfree(copy);
 
 	switch (a)
 	{

base-commit: 284925508ae685a63ee056f89a336caecab64a63
-- 
2.53.0

From c8fd6666836c7696c49708e1afb785cad3b55f13 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 8 Dec 2025 08:13:51 +0100
Subject: [PATCH v10 2/4] Use typeof everywhere instead of compiler specific
 spellings

index a4fee84398d..df01acdf6ba 100644

From 8abacdb5305619a19fb3dc7eaa53d4cc4d7d805e Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Thu, 19 Feb 2026 23:55:43 +0100
Subject: [PATCH v10 3/4] Make unconstify and unvolatize use
 StaticAssertVariableIsOfTypeMacro

index df01acdf6ba..c92b214f6b0 100644
 
 /*
-- 
2.53.0

From 437a8aece153c5dc24c0e2708c034fe1718ea3bf Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 13 Feb 2026 09:39:34 +0100
Subject: [PATCH v10 4/4] Add C++ version of __builtin_types_compatible_p

StaticAssertVariableIsOfType would fail to compile in C++ extensions if
meson/autoconf has detected HAVE__BUILTIN_TYPES_COMPATIBLE_P to be true
for the C compiler. Basically equivalent to the PG_PRINTF_ATTRIBUTE
situation in 0909380e4c9.

This fixes that problem by defining __builtin_types_compatible_p in C++
using an equivalent STL constructs.
---
 src/include/c.h                               | 22 +++++++++++++++++++
 .../test_cplusplusext/test_cplusplusext.cpp   | 20 ++++++++++++++++-
 2 files changed, 41 insertions(+), 1 deletion(-)

diff --git a/src/include/c.h b/src/include/c.h
index c92b214f6b0..ba629a3503b 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -80,6 +80,12 @@
 #ifdef HAVE_XLOCALE_H
 #include <xlocale.h>
 #endif
+#ifdef __cplusplus
+extern "C++"
+{
+#include <type_traits>
+}
+#endif
 #ifdef ENABLE_NLS
 #include <libintl.h>
 #endif
@@ -443,7 +449,23 @@
 #ifndef HAVE_TYPEOF
 #define HAVE_TYPEOF 1
 #endif
+/*
+ * Provide __builtin_types_compatible_p for C++ by comparing types with
+ * std::is_same after stripping cv-qualifiers. The second branch handles
+ * GCC's special case where an incomplete array (e.g. int[]) is considered
+ * compatible with a complete array of the same element type (e.g. int[3]).
+ */
+#define __builtin_types_compatible_p(x, y) \
+	(std::is_same<std::remove_cv_t<x>, std::remove_cv_t<y>>::value || \
+	 (std::is_array<x>::value && std::is_array<y>::value && \
+	  (std::extent<x>::value == 0 || std::extent<y>::value == 0) && \
+	  std::is_same<std::remove_cv_t<std::remove_extent_t<x>>, \
+				   std::remove_cv_t<std::remove_extent_t<y>>>::value))
+#ifndef HAVE__BUILTIN_TYPES_COMPATIBLE_P
+#define HAVE__BUILTIN_TYPES_COMPATIBLE_P 1
 #endif
+#endif
+
 
 /*
  * CppAsString
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index ea04a761184..936ca422c76 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -34,11 +34,14 @@ StaticAssertDecl(sizeof(int32) == 4, "int32 should be 4 bytes");
 extern "C" Datum
 test_cplusplus_add(PG_FUNCTION_ARGS)
 {
-	int32		a = PG_GETARG_INT32(0);
+	const int32		a = PG_GETARG_INT32(0);
 	int32		b = PG_GETARG_INT32(1);
+	const char *p = "";
 	RangeTblRef *node = makeNode(RangeTblRef);
 	RangeTblRef *copy = copyObject(node);
 	List	   *list = list_make1(node);
+	const int32 int_array[3] = {0};
+	extern int32 incomplete_array[];
 
 	foreach_ptr(RangeTblRef, rtr, list)
 	{
@@ -52,6 +55,21 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 
 	StaticAssertStmt(sizeof(int32) == 4, "int32 should be 4 bytes");
 	(void) StaticAssertExpr(sizeof(int64) == 8, "int64 should be 8 bytes");
+	StaticAssertVariableIsOfType(a, int32);
+	StaticAssertVariableIsOfType(a, const int32);
+	StaticAssertVariableIsOfType(b, const int32);
+	StaticAssertVariableIsOfType(int_array, int32[3]);
+	StaticAssertVariableIsOfType(int_array, const int32[3]);
+	StaticAssertVariableIsOfType(p, const char *);
+	StaticAssertVariableIsOfTypeMacro(a, int32);
+	StaticAssertVariableIsOfTypeMacro(a, const int32);
+	StaticAssertVariableIsOfTypeMacro(b, int32);
+	StaticAssertVariableIsOfTypeMacro(int_array, int32[3]);
+	StaticAssertVariableIsOfTypeMacro(int_array, const int32[3]);
+	StaticAssertVariableIsOfTypeMacro(p, const char *);
+	/* incomplete array matches complete array type */
+	StaticAssertVariableIsOfType(incomplete_array, int32[3]);
+	StaticAssertVariableIsOfTypeMacro(incomplete_array, int32[3]);
 
 	list_free(list);
 	pfree(node);
-- 
2.53.0

---

From: Peter Eisentraut <[email-masked]>
Date: Fri, 27 Feb 2026 17:52:46 +0100

> __builtin_types_compatible_p.

Have you tested whether/how these configure/meson tests work if no C++ 
compiler is found?

(I haven't, I'm just wondering.)

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Sat, 28 Feb 2026 12:31:24 +0100

> (I haven't, I'm just wondering.)

I hadn't before, but done now. It compiles postgres without problems
(but without the cplusplusext extension ofcourse). For meson the new
meson checks are inside have_cxx, so they are not run at all. For
configure the checks are run, but they detect typeof as not available
in C++ (because its unable to compile the test program).

---

From: Peter Eisentraut <[email-masked]>
Date: Mon, 2 Mar 2026 11:56:34 +0100

> __builtin_types_compatible_p.

I have committed v10-0001.  Now let's give the buildfarm a few days.

---

From: Peter Eisentraut <[email-masked]>
Date: Fri, 6 Mar 2026 10:23:11 +0100

> I have committed v10-0001.  Now let's give the buildfarm a few days.

I have committed v10-0002 and v10-0003 now.  I will look at the 
remaining patch in a few days.

---

From: Peter Eisentraut <[email-masked]>
Date: Mon, 9 Mar 2026 09:39:38 +0100

> remaining patch in a few days.

Thoughts on v10-0004:

It's not clear to me to what extent StaticAssertVariableIsOfType would 
be useful in C++.  There are two general areas where it is used.  One, 
when multiple separate extensions want to talk to each other, to check 
the types of certain entry point variables, such as 
plpython/hstore_plpython.  And two, in some macro-based template 
libraries such as lib/ilist.h and lib/pairingheap.h.  You add a lot of 
tests, but do they cover these particular use scenarios?

In either case, it might be better to create a test on that level and 
then see what we'd need to make happen to have it working under C++. 
There is lots of trickery involved there, so it's not clear whether it 
works out of the box in C++ already.

Are there any of these that you are particularly interested in for your 
work?

About the specific implementation, I'm hesitant to build this on top of 
__builtin_types_compatible_p().  Aside from the ugliness of redefining a 
symbol that starts with __builtin_*, I think we should really work to 
get rid of __builtin_types_compatible_p() and replace it with _Generic, 
which would be portable beyond GCC.

How about we make a pg_types_compatible_p(), which would look like this:

#if defined(__cplusplus)

// your C++ code here

#else if defined(HAVE__GENERIC)

// C code using _Generic here

#else

// C code using __builtin_types_compatible_p here

#endif

We can require that a supported C compiler must support either _Generic 
or __builtin_types_compatible_p, so we don't need any further fallback. 
(So we could remove the configure test of __builtin_types_compatible_p, 
but we'd add one for _Generic.)  And in a few years we could even remove 
the __builtin_types_compatible_p fallback.

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 13 Mar 2026 10:08:08 +0100

On Mon, 9 Mar 2026 at 09:39, Peter Eisentraut <[email-masked]> wrote:
> I think we should really work to

I initially intended to do this, but sadly using _Generic inside our
static assert constructs (even the new version you added) causes
internal compiler errors on MSVC 19... I agree with your other
feedback, and I think it's probably best to retract this patch (I've
marked it as committeed in the commitfest now, because of all the
other patches). The main reason I cared about this was to have a
_Generic based macro for type comparisons, which I could use in other
patches. I'll just create some there instead.

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 23 Mar 2026 10:58:34 +0100

On Mon, 23 Mar 2026 at 10:27, Peter Eisentraut <[email-masked]> wrote:
> I think we should commit the pg_list.h changes, since the C-style
> compound literals are not a C++ feature at all, and so without this MSVC
> would never get supported.  (Or you couldn't use PostgreSQL lists, which
> would be very limiting.)

Sounds good to me.

> The other changes deal with designated initializers and flexible array
> members.  These are not a blocker, since extension authors could deal
> with them themselves by adding appropriate compiler options or similar.

I think we should add these flags to CXXFLAGS for MSVC by default,
similar to how we add -std=gnu++11/-std=c++11 for other compilers. We
can then document on the C++ extension docs page, that MSVC compilers
require C++20 support.

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Mon, 30 Mar 2026 00:53:48 +0200

On Fri, 27 Mar 2026 at 12:23, Peter Eisentraut <[email-masked]> wrote:
> Here is another tidied up patch set for this.  I didn't go quite as far
> as enabling C++20 by default in meson.build, this would just take more
> time to work out and test all the different combinations, but I added
> the flag to the Cirrus CI task, since there we know what compiler we have.

I think 0001 and 0002 are good.

0003 seems awkward though. Attached is an approach that I think is
better: It actually checks for the required featureset and adds the
necessary flags to the compiler.

I also added a small patch in 0004 to align configure and meson
behaviour when no sufficiently modern compiler is found.
From 6b04f2bc87231998946ff0736013a98c9f23df2c Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Fri, 27 Mar 2026 11:45:36 +0100
Subject: [PATCH v12 1/4] meson: Make room for C++-only warning flags for MSVC

Refactor the MSVC warning option handling to have a list of common
flags and lists of flags specific to C and C++.
---
 meson.build | 31 +++++++++++++++++++++++--------
 1 file changed, 23 insertions(+), 8 deletions(-)

diff --git a/meson.build b/meson.build
index ea31cbce9c0..42b0a9a6a71 100644
--- a/meson.build
+++ b/meson.build
@@ -2293,27 +2293,42 @@ endforeach

 if cc.get_id() == 'msvc'
-  cflags_warn += [
+  msvc_common_warning_flags = [
+    # Disable warnings in system headers
+    '/external:anglebrackets',
+    '/external:W0',
+
     # Warnings to disable:
-    # from /W1:
-    '/wd4090', # different 'modifier' qualifiers
     # from /W2:
     '/wd4244', # conversion from 'type1' to 'type2', possible loss of data
+
+    # Additional warnings to enable:
+    '/w24062', # enumerator 'identifier' in switch of enum 'enumeration' is not handled [like -Wswitch]
+    '/w24102', # unreferenced label [like -Wunused-label]
+  ]
+
+  msvc_c_warning_flags = [
+    # Warnings to disable:
+    # from /W1:
+    '/wd4090', # different 'modifier' qualifiers
     # from /W3:
     '/wd4018', # signed/unsigned mismatch
     '/wd4101', # unreferenced local variable [like -Wunused-variable, but there is no "unused" attribute, so too noisy]
     '/wd4267', # conversion from 'size_t' to 'type', possible loss of data
 
     # Additional warnings to enable:
-    '/w24062', # enumerator 'identifier' in switch of enum 'enumeration' is not handled [like -Wswitch]
-    '/w24102', # unreferenced label [like -Wunused-label]
     '/w24255', # 'function' : no function prototype given: converting '()' to '(void)' [like -Wstrict-prototypes]
+  ]
 
-    # Disable warnings in system headers
-    '/external:anglebrackets',
-    '/external:W0',
+  msvc_cxx_warning_flags = [
   ]
 
+  cflags_warn += msvc_common_warning_flags
+  cflags_warn += msvc_c_warning_flags
+
+  cxxflags_warn += msvc_common_warning_flags
+  cxxflags_warn += msvc_cxx_warning_flags
+
   cppflags += [
     '/DWIN32',
     '/DWINDOWS',

base-commit: 10e4d8aaf46fb46b8b78e026560b68af84a6495b
-- 
2.53.0

From 366dfbc11fd047f75107a87023b39a58fa7a2326 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Thu, 26 Mar 2026 08:35:45 +0100
Subject: [PATCH v12 2/4] Disable some C++ warnings in MSVC

Flexible array members, as used in many PostgreSQL header files, are
not a C++ feature.  MSVC warns about these.  Disable the
warning.  (GCC and Clang accept them, but they would warn in -pedantic
mode.)
---
 meson.build | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/meson.build b/meson.build
index 42b0a9a6a71..64a5bb888d6 100644
--- a/meson.build
+++ b/meson.build
@@ -2321,6 +2321,9 @@ if cc.get_id() == 'msvc'
   ]
 
   msvc_cxx_warning_flags = [
+    # Warnings to disable:
+    # from /W2:
+    '/wd4200', # nonstandard extension used: zero-sized array in struct/union [widely used in PostgreSQL C headers]
   ]
 
   cflags_warn += msvc_common_warning_flags
-- 
2.53.0

From df6d9e8d1bb955d9cffe3095cc27d4209de1ce33 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Sun, 29 Mar 2026 13:01:37 +0200
Subject: [PATCH v12 3/4] Add support for C++ extensions with MSVC

To build C++ extensions we need support for designated initializers,
because PG_MODULE_MAGIC uses it. Designated initializers only got
standardized in C++20. In GCC and Clang they also work when using
earlier C++ versions, but MSVC really only supports them when its
configured to be in C++20 mode or higher.

This extends C++11 feature test to also check for designated initializer
support.

When passing both -pedantic and -Werror even Clang and GCC can fail this
new feature test if they're old enough that they don't compile with
C++20 by default. So this also changes meson and configure to try C++20
if using C++11 didn't work. Before this patch this such a setup would
pass the C++11 check but would then fail to compile test_cplusplusext.
---
 configure.ac                                  | 13 ++++++----
 doc/src/sgml/xfunc.sgml                       |  5 ++++
 meson.build                                   | 26 +++++++++++--------
 .../modules/test_cplusplusext/meson.build     |  5 ----
 4 files changed, 28 insertions(+), 21 deletions(-)

diff --git a/configure.ac b/configure.ac
index 2baac5e9da7..e4570a805c6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -398,17 +398,20 @@ AC_SUBST(have_cxx)
 
 if test "$have_cxx" = yes; then
 
-# Detect option needed for C++11
-AC_MSG_CHECKING([for $CXX option to accept ISO C++11])
+# Detect option needed for C++11 with designated initializers. We need
+# designated initializers because we use them in many of our headers, including
+# in the PG_MODULE_MAGIC definition.
+AC_MSG_CHECKING([for $CXX option to accept ISO C++11 with designated initializers])
 AC_CACHE_VAL([pgac_cv_prog_cxx_cxx11],
 [pgac_cv_prog_cxx_cxx11=no
 pgac_save_CXX=$CXX
 AC_LANG_PUSH([C++])
-for pgac_arg in '' '-std=gnu++11' '-std=c++11'; do
+for pgac_arg in '' '-std=gnu++11' '-std=c++11' '-std=gnu++20' '-std=c++20'; do
   CXX="$pgac_save_CXX $pgac_arg"
   AC_COMPILE_IFELSE([AC_LANG_SOURCE([[#if !defined __cplusplus || __cplusplus < 201103L
 # error "Compiler does not advertise C++11 conformance"
-#endif]])], [[pgac_cv_prog_cxx_cxx11=$pgac_arg]])
+#endif
+struct S { int x; } s = { .x = 1 };]])], [[pgac_cv_prog_cxx_cxx11=$pgac_arg]])
   test x"$pgac_cv_prog_cxx_cxx11" != x"no" && break
 done
 AC_LANG_POP([C++])
@@ -416,7 +419,7 @@ CXX=$pgac_save_CXX])
 
 if test x"$pgac_cv_prog_cxx_cxx11" = x"no"; then
   AC_MSG_RESULT([unsupported])
-  AC_MSG_WARN([C++ compiler "$CXX" does not support C++11])
+  AC_MSG_WARN([C++ compiler "$CXX" does not support C++11 with designated initializers])
   have_cxx=no
 elif test x"$pgac_cv_prog_cxx_cxx11" = x""; then
   AC_MSG_RESULT([none needed])
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 70e815b8a2c..6895fe483c1 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -4026,6 +4026,11 @@ extern PgStat_Kind pgstat_register_kind(PgStat_Kind kind,
 
      <itemizedlist>
       <listitem>
+       <para>
+         You need a C++ compiler that supports C++11 and designated
+         initializers (GCC and Clang support this in C++11 mode, but MSVC only
+         supports it in C++20 mode).
+       </para>
        <para>
          All functions accessed by the backend must present a C interface
          to the backend;  these C functions can then call C++ functions.
diff --git a/meson.build b/meson.build
index 64a5bb888d6..bcad5f4fc01 100644
--- a/meson.build
+++ b/meson.build
@@ -647,29 +647,33 @@ if not cc.compiles(c11_test, name: 'C11')
 endif

-# Do we need an option to enable C++11?
-cxx11_test = '''
+# Do we need an option to enable C++11 and/or designated initializers? We need
+# designated initiliazers because we use them in many of our headers, including
+# in the PG_MODULE_MAGIC definition. Clang and gcc support designated
+# initializers in C++11 mode, but MSVC only supports them in C++20 mode.
+cxx_features_test = '''
 #if !defined __cplusplus || __cplusplus < 201103L
 # error "Compiler does not advertise C++11 conformance"
 #endif
+struct S { int x; } s = { .x = 1 };
 '''
 
-if have_cxx and not cxx.compiles(cxx11_test, name: 'C++11')
-  cxx11_ok = false
+if have_cxx and not cxx.compiles(cxx_features_test, name: 'C++11 with designated initializers')
+  cxx_features_ok = false
   if cxx.get_id() == 'msvc'
-    cxx11_test_args = ['/std:c++14']  # oldest version supported
+    cxx_features_test_args = ['/std:c++20']
   else
-    cxx11_test_args = ['-std=gnu++11', '-std=c++11']
+    cxx_features_test_args = ['-std=gnu++11', '-std=c++11', '-std=gnu++20', 'std=c++20']
   endif
-  foreach arg : cxx11_test_args
-    if cxx.compiles(cxx11_test, name: 'C++11 with @0@'.format(arg), args: [arg])
-      cxx11_ok = true
+  foreach arg : cxx_features_test_args
+    if cxx.compiles(cxx_features_test, name: 'C++11 with designated initializers with @0@'.format(arg), args: [arg])
+      cxx_features_ok = true
       cxxflags += arg
       break
     endif
   endforeach
-  if not cxx11_ok
-    error('C++ compiler does not support C++11')
+  if not cxx_features_ok
+    error('C++ compiler does not support C++11 with designated initializers')
   endif
 endif

From 3eeeabc4335d2ceef777b20bb1460a38a82baf2d Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 30 Mar 2026 00:45:53 +0200
Subject: [PATCH v12 4/4] meson: Consider an insufficient C++ compiler a
 warning

Meson and configure were handling the case of a C++ compiler with an
insufficient feature set differently. In configure, it would be a
warning, but meson would throw a hard error. This makes it a warning in
meson too.
---
 meson.build | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/meson.build b/meson.build
index bcad5f4fc01..ef1d7ed9a57 100644
--- a/meson.build
+++ b/meson.build
@@ -673,7 +673,8 @@ if have_cxx and not cxx.compiles(cxx_features_test, name: 'C++11 with designated
     endif
   endforeach
   if not cxx_features_ok
-    error('C++ compiler does not support C++11 with designated initializers')
+    warning('C++ compiler does not support C++11 with designated initializers')
+    have_cxx = false
   endif
 endif
 
-- 
2.53.0

---

From: Peter Eisentraut <[email-masked]>
Date: Tue, 31 Mar 2026 10:33:36 +0200

> necessary flags to the compiler.

Hmm, note that C++ is also used for LLVM/JIT, and by requiring this 
additional feature set we are also imposing new requirements for those 
users.  This has not been fully explored, and I hesitate to add such a 
new requirement at the last moment.

But how about this: We add the feature test that you propose and enable 
the extension based on that.  See attached patch.  This reduces to 
essentially a three-line patch, much simpler than all previous proposals.
From ec7e12b1c0e53a9a1a46d1f43479fdf71d4ba8eb Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Tue, 31 Mar 2026 09:48:52 +0200
Subject: [PATCH] Enable test_cplusplusext with MSVC

The test_cplusplusext test module has so far been disabled on MSVC.
The only remaining problem now is that designated initializers, as
used in PG_MODULE_MAGIC, require C++20.  (With GCC and Clang they work
in older C++ versions as well.)

This adds another test in the top-level meson.build to check that the
compiler supports C++20 designated initializers.  This is not
required, we are just checking and recording the answer.  If yes, we
can enable the test module.

Most current compilers likely won't be in C++20 mode by default.  This
doesn't change that; we are not doing anything to try to switch the
compiler into that mode.  This might be a separate project, but for
now we'll leave that for the user or the test scaffolding.

The VS task on Cirrus CI is changed to provide the required flag to
turn on C++20 mode.

Discussion: https://www.postgresql.org/message-id/flat/CAGECzQR21OnnKiZO_1rLWO0-16kg1JBxnVq-wymYW0-_1cUNtg%40mail.gmail.com
---
 .cirrus.tasks.yml                              |  1 +
 meson.build                                    | 14 ++++++++++++++
 src/test/modules/test_cplusplusext/meson.build |  7 +------
 3 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index 0f32827952f..a22cef063f3 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -782,6 +782,7 @@ task:
     CIRRUS_WINDOWS_ERROR_MODE: 0x8001
 
     MESON_FEATURES:
+      -Dcpp_args=/std:c++20
       -Dauto_features=disabled
       -Dldap=enabled
       -Dssl=openssl
diff --git a/meson.build b/meson.build
index c4b4cd4af06..8b134b28a69 100644
--- a/meson.build
+++ b/meson.build
@@ -2176,6 +2176,20 @@ choke me
 endif

+# Check whether the C++ compiler supports designated initializers.
+# These are used by PG_MODULE_MAGIC, and we use the result of this
+# test to decide whether to enable the test_cplusplusext test module.
+# Designated initializers only got standardized in C++20.  In GCC and
+# Clang they also work when using earlier C++ versions, but MSVC
+# really only supports them when its configured to be in C++20 mode or
+# higher.
+if have_cxx
+  have_cxx_desinit = cxx.compiles('struct S { int x; } s = { .x = 1 };', name: 'C++ designated initializers')
+else
+  have_cxx_desinit = false
+endif
+
+
 
 ###############################################################
 # Compiler flags
diff --git a/src/test/modules/test_cplusplusext/meson.build b/src/test/modules/test_cplusplusext/meson.build
index d13210ca593..5860464a503 100644
--- a/src/test/modules/test_cplusplusext/meson.build
+++ b/src/test/modules/test_cplusplusext/meson.build
@@ -1,11 +1,6 @@
 # Copyright (c) 2025-2026, PostgreSQL Global Development Group
 
-if not have_cxx
-  subdir_done()
-endif
-
-# Currently not supported, to be fixed.
-if cc.get_id() == 'msvc'
+if not have_cxx or not have_cxx_desinit
   subdir_done()
 endif
 
-- 
2.53.0

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Tue, 31 Mar 2026 11:09:59 +0200

> new requirement at the last moment.

I understand the hesitation, but in practice we already impose those
requirements anyway for non-MSVC compilers because test_cplusplusext
doesn't compile without it. So any compiler that would cause problems
with the new featureset would have already complained about that
before.

> essentially a three-line patch, much simpler than all previous proposals.

If you're still worried, then this sounds like a fine middle-ground
for now. I think we should reconsider adding the automatic /std:c++20
flag for the PG20 dev cycle though.

---

From: Peter Eisentraut <[email-masked]>
Date: Wed, 1 Apr 2026 07:54:07 +0200

> flag for the PG20 dev cycle though.

Ok, I have committed the shown patch.

---

From: Bertrand Drouvot <[email-masked]>
Date: Thu, 9 Apr 2026 11:41:39 +0000

Hi,

> Ok, I have committed the shown patch.

d50c86e74375 added a comment mentionning that StaticAssertStmt is deprecated, so
we really need the one added in test_cplusplusext.cpp?

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com

---

From: Bertrand Drouvot <[email-masked]>
Date: Tue, 14 Apr 2026 13:10:12 +0000

Hi,

> we really need the one added in test_cplusplusext.cpp?

Now that 66ad764c8d5 is in, the only remaining use of StaticAssertStmt() is in
test_cplusplusext.cpp. What about the attached to get rid of it? 

Regards,

---

From: Peter Eisentraut <[email-masked]>
Date: Thu, 16 Apr 2026 10:07:35 +0200

> test_cplusplusext.cpp. What about the attached to get rid of it?

This is a test module.  Even if the construct is deprecated, we can 
still test it.

---

From: Bertrand Drouvot <[email-masked]>
Date: Thu, 16 Apr 2026 11:31:27 +0000

Hi,

On Thu, Apr 16, 2026 at 10:07:35AM +0200, Peter Eisentraut wrote:
> This is a test module.  Even if the construct is deprecated, we can still
> test it.

Right, but I was thinking that if we introduce a "pg_attribute_deprecated" macro
(like discussed in [1] and [2]) and make use of it for StaticAssertStmt(), then
this test module would generate Warnings.

We can still come back to it should the above be implemented.

[1]: https://postgr.es/m/CA%2BhUKGK2zuRevnNzCpVzLA7ieHnJoYPnDvgtWRcB4pVnOzchhQ%40mail.gmail.com
[2]: https://postgr.es/m/[email-masked]

Regards,