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 protected]>
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 protected]>
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 protected]>
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 protected]>
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 protected]>
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

Reply via email to