Subject: Make copyObject work in C++

From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 5 Dec 2025 15:46:57 +0100

Calling copyObject fails in C++ with an error like in most setups:

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 using decltype instead of typeof when
including the header in C++.

Realized because of Thomas' not about how much of our headers should
work in C++, and remembering I hit this specific problem myself.

Another approach would be to force the value of HAVE_TYPEOF to 0 if __cplusplus.

---

From: Tom Lane <[email-masked]>
Date: Sun, 07 Dec 2025 14:45:07 -0500

> including the header in C++.

Hmm, this only fixes the one use-case.  Admittedly we have only one
use-case, but as soon as we have another we'll have a new problem.
How about instead modifying the macro?  Maybe something like this
in c.h (untested):

#ifdef __cplusplus
#undef typeof
#define typeof decltype
#define HAVE_TYPEOF 1
#endif

> Another approach would be to force the value of HAVE_TYPEOF to 0 if __cplusplus.

That would be sad, because we'd lose the type checking ability
of copyObject() in C++ code.

BTW, grepping finds a number of random references to __typeof__ and
__typeof, which probably ought to be updated to be just typeof.

			regards, tom lane

---

From: Peter Eisentraut <[email-masked]>
Date: Mon, 8 Dec 2025 08:57:17 +0100

> Another approach would be to force the value of HAVE_TYPEOF to 0 if __cplusplus.

In the long run, I would like to change copyObject() to use 
typeof_unqual instead, because that handles qualifiers more correctly. 
(Currently, copyObject() of a const-qualified pointer results in a 
const-qualified pointer, which is nonsensical because the reason you 
made the copy is that you can modify it.)  See attached patch for an 
example.  Does C++ have something that is semantically similar to that?

From 1e733f16f116be638cbd4f6a359a01541c9a5d24 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Mon, 8 Dec 2025 08:33:18 +0100
Subject: [PATCH] Change copyObject() to use typeof_unqual

The new implementation ensures that the result of copyObject() is not
const-qualified when the input is.  This was previously incorrect,
since the point of copyObject() is to make a copy to mutate, but
apparently no code ran into it.
---
 config/c-compiler.m4       | 25 +++++++++++++++++++++++
 configure                  | 42 ++++++++++++++++++++++++++++++++++++++
 configure.ac               |  1 +
 meson.build                | 24 ++++++++++++++++++++++
 src/include/nodes/nodes.h  |  4 ++--
 src/include/pg_config.h.in |  7 +++++++
 6 files changed, 101 insertions(+), 2 deletions(-)

diff --git a/config/c-compiler.m4 b/config/c-compiler.m4
index 236a59e8536..8ee860c9091 100644
--- a/config/c-compiler.m4
+++ b/config/c-compiler.m4
@@ -160,6 +160,31 @@ if test "$pgac_cv_c_typeof" != no; then
 fi])# PGAC_C_TYPEOF

+# PGAC_C_TYPEOF_UNQUAL
+# --------------------
+# Check if the C compiler understands typeof_unqual or a variant.  Define
+# HAVE_TYPEOF_UNQUAL if so, and define 'typeof_unqual' to the actual key word.
+#
+AC_DEFUN([PGAC_C_TYPEOF_UNQUAL],
+[AC_CACHE_CHECK(for typeof_unqual, pgac_cv_c_typeof_unqual,
+[pgac_cv_c_typeof_unqual=no
+for pgac_kw in typeof_unqual __typeof_unqual__; do
+  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
+[int x = 0;
+$pgac_kw(x) y;
+y = x;
+return y;])],
+[pgac_cv_c_typeof_unqual=$pgac_kw])
+  test "$pgac_cv_c_typeof_unqual" != no && break
+done])
+if test "$pgac_cv_c_typeof_unqual" != no; then
+  AC_DEFINE(HAVE_TYPEOF_UNQUAL, 1,
+            [Define to 1 if your compiler understands `typeof_unqual' or something similar.])
+  if test "$pgac_cv_c_typeof_unqual" != typeof_unqual; then
+    AC_DEFINE_UNQUOTED(typeof_unqual, $pgac_cv_c_typeof_unqual, [Define to how the compiler spells `typeof_unqual'.])
+  fi
+fi])# PGAC_C_TYPEOF_UNQUAL
+
 
 # PGAC_C_TYPES_COMPATIBLE
 # -----------------------
diff --git a/configure b/configure
index 3a0ed11fa8e..af856b20f0b 100755
--- a/configure
+++ b/configure
@@ -14787,6 +14787,48 @@ _ACEOF
 
   fi
 fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for typeof_unqual" >&5
+$as_echo_n "checking for typeof_unqual... " >&6; }
+if ${pgac_cv_c_typeof_unqual+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  pgac_cv_c_typeof_unqual=no
+for pgac_kw in typeof_unqual __typeof_unqual__; 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_c_try_compile "$LINENO"; then :
+  pgac_cv_c_typeof_unqual=$pgac_kw
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  test "$pgac_cv_c_typeof_unqual" != no && break
+done
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_c_typeof_unqual" >&5
+$as_echo "$pgac_cv_c_typeof_unqual" >&6; }
+if test "$pgac_cv_c_typeof_unqual" != no; then
+
+$as_echo "#define HAVE_TYPEOF_UNQUAL 1" >>confdefs.h
+
+  if test "$pgac_cv_c_typeof_unqual" != typeof_unqual; then
+
+cat >>confdefs.h <<_ACEOF
+#define typeof_unqual $pgac_cv_c_typeof_unqual
+_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 c2413720a18..8eced24beb5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1676,6 +1676,7 @@ AC_C_INLINE
 PGAC_PRINTF_ARCHETYPE
 PGAC_C_STATIC_ASSERT
 PGAC_C_TYPEOF
+PGAC_C_TYPEOF_UNQUAL
 PGAC_C_TYPES_COMPATIBLE
 PGAC_C_BUILTIN_CONSTANT_P
 PGAC_C_BUILTIN_OP_OVERFLOW
diff --git a/meson.build b/meson.build
index 6e7ddd74683..85b98ad81db 100644
--- a/meson.build
+++ b/meson.build
@@ -2818,6 +2818,30 @@ int main(void)
   endif
 endforeach
 
+# Check if the C compiler understands typeof_unqual or a variant.  Define
+# HAVE_TYPEOF_UNQUAL if so, and define 'typeof_unqual' to the actual key word.
+foreach kw : ['typeof_unqual', '__typeof_unqual__']
+  if cc.compiles('''
+int main(void)
+{
+    int x = 0;
+    @0@(x) y;
+    y = x;
+    return y;
+}
+'''.format(kw),
+    name: kw,
+    args: test_c_args, include_directories: postgres_inc)
+
+    cdata.set('HAVE_TYPEOF_UNQUAL', 1)
+    if kw != 'typeof_unqual'
+      cdata.set('typeof_unqual', kw)
+    endif
+
+    break
+  endif
+endforeach
+
 
 # Even though restrict is in C99 and should be supported by all
 # supported compilers, this indirection is useful because __restrict
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fb3957e75e5..0ac0be1b288 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 HAVE_TYPEOF_UNQUAL
+#define copyObject(obj) ((typeof_unqual(*(obj)) *) copyObjectImpl(obj))
 #else
 #define copyObject(obj) copyObjectImpl(obj)
 #endif
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index b0b0cfdaf79..503467ca092 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -472,6 +472,10 @@
 /* Define to 1 if your compiler understands `typeof' or something similar. */
 #undef HAVE_TYPEOF
 
+/* Define to 1 if your compiler understands `typeof_unqual' or something
+   similar. */
+#undef HAVE_TYPEOF_UNQUAL
+
 /* Define to 1 if you have the <uchar.h> header file. */
 #undef HAVE_UCHAR_H
 
@@ -815,3 +819,6 @@
 
 /* Define to how the compiler spells `typeof'. */
 #undef typeof
+
+/* Define to how the compiler spells `typeof_unqual'. */
+#undef typeof_unqual
-- 
2.52.0

---

From: Peter Eisentraut <[email-masked]>
Date: Mon, 8 Dec 2025 09:00:55 +0100

> How about instead modifying the macro?  Maybe something like this

AFAICT, both gcc and clang support typeof in C++ mode as well.  So this 
kind of renaming could be confusing.

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Mon, 08 Dec 2025 09:11:59 +0100

> #endif

Went with defining pg_exprtype (pg_typeof already exists). Undefining
typeof seemed a bit heavy-handed. Especially since I think it would be
nice to backport this so C++ extensions can use copyObject directly.

> __typeof, which probably ought to be updated to be just typeof.

Added a follow on patch that starts using pg_exrtype in all those
places.
From 5a076be8b919034c7ff469e39a92d76c78590a59 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 5 Dec 2025 15:37:59 +0100
Subject: [PATCH v2 1/2] Make copyObject work in C++

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 one 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 ++--
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/src/include/c.h b/src/include/c.h
index ccd2b654d45..46a97f11ae7 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -407,6 +407,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 fb3957e75e5..a8f42431828 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

base-commit: 31280d96a64850f5a9a924088890ab43a2905237
-- 
2.52.0

From 62f920e519004d692b14659c5d00546baa6be87c Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 8 Dec 2025 08:13:51 +0100
Subject: [PATCH v2 2/2] Use pg_exprtype instead of __typeof__ or __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 | 16 ++++------------
 2 files changed, 8 insertions(+), 16 deletions(-)

diff --git a/src/include/c.h b/src/include/c.h
index 46a97f11ae7..230ea2d10af 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -987,10 +987,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) \
@@ -1238,11 +1238,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 ea340fee657..dac61b13118 100644
--- a/src/include/utils/relptr.h
+++ b/src/include/utils/relptr.h
@@ -38,16 +38,12 @@
 #define relptr_declare(type, relptrtype) \
 	typedef relptr(type) relptrtype
 
-#ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P
+#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
-/*
- * If we don't have __builtin_types_compatible_p, assume we might not have
- * __typeof__ either.
- */
 #define relptr_access(base, rp) \
 	(AssertVariableIsOfTypeMacro(base, char *), \
 	 (void *) ((rp).relptr_off == 0 ? NULL : (base) + (rp).relptr_off - 1))
@@ -72,16 +68,12 @@ relptr_store_eval(char *base, char *val)
 	}
 }
 
-#ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P
+#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
-/*
- * If we don't have __builtin_types_compatible_p, assume we might not have
- * __typeof__ either.
- */
 #define relptr_store(base, rp, val) \
 	(AssertVariableIsOfTypeMacro(base, char *), \
 	 (rp).relptr_off = relptr_store_eval((base), (char *) (val)))
-- 
2.52.0

---

From: David Geier <[email-masked]>
Date: Mon, 8 Dec 2025 09:33:10 +0100

> example.  Does C++ have something that is semantically similar to that?

Since C++11 there's std::remove_const which can be used as
std::remove_const<decltype(type)>::type.

I'm not aware of anything pre C++11, except for rolling your own variant
of std::remove_const via template specialization.

--
David Geier

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 8 Dec 2025 09:52:17 +0100

> of std::remove_const via template specialization.

I think depending on C++11 sounds fine, since we're also depending on
C11 and people tend to use much more recent C++ versions than C
versions (so probably we could even require something higher).

---

From: Tom Lane <[email-masked]>
Date: Mon, 08 Dec 2025 09:51:06 -0500

> kind of renaming could be confusing.

Hm, if that's true then we should not have to do anything ...
so why is Jelte reporting a problem?

			regards, tom lane

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 8 Dec 2025 16:31:30 +0100

> so why is Jelte reporting a problem?

Seems it's related to -std=c++17 vs -std=gnu++17. I was compiling my
code with the former, which throws the error in question[1]. Compiling
with the latter works fine[2].

So I guess it depends what we want to require from C++ extensions.
Should we require them to compile with gnu extensions? My opinion on
that would be no.

[1]: https://godbolt.org/z/fz567hs1r
[2]: https://godbolt.org/z/cq1se55bn

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Tue, 9 Dec 2025 13:58:40 +0100

> example.  Does C++ have something that is semantically similar to that?

Yes, there's a std::remove_cv, std::remove_const, and std::remove_volatile[1].

[1]: https://en.cppreference.com/w/cpp/types/remove_cv.html

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Sun, 14 Dec 2025 17:56:23 +0100

> nice to backport this so C++ extensions can use copyObject directly.

Rebased version of this patchset after conflicts from 315342ffed.
From 4d4e137b4712b48bf3b7c5a19813c1956a28538e Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 5 Dec 2025 15:37:59 +0100
Subject: [PATCH v3 1/2] Make copyObject work in C++

diff --git a/src/include/c.h b/src/include/c.h
index d2cdc76644c..040f2ffc32b 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -418,6 +418,19 @@
 #define unlikely(x) ((x) != 0)
 #endif
 
+/*
+ * pg_exprtype

base-commit: c5ae07a90a0f3594e5053a26f3c99b041df427d3
-- 
2.52.0

From 8a4b4749aa87dff7e4e40d2eeb21b84f97811738 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 8 Dec 2025 08:13:51 +0100
Subject: [PATCH v3 2/2] 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(-)

index 040f2ffc32b..f0800669f09 100644
@@ -997,10 +997,10 @@ pg_noreturn extern void ExceptionalCondition(const char *conditionName,
@@ -1248,11 +1248,11 @@ typedef struct PGAlignedXLogBlock
index 48e394dba71..dac61b13118 100644
@@ -38,10 +38,10 @@
 
-#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
-	 AssertVariableIsOfTypeMacro(val, typeof((rp).relptr_type)), \

---

From: Peter Eisentraut <[email-masked]>
Date: Tue, 16 Dec 2025 13:28:27 +0100

> work in C++, and remembering I hit this specific problem myself.

I think it might be good to create a test extension written in C++, like 
under src/test/modules/, and sprinkle it with various constructs like 
copyObject() and static assertions, and whatever else we find that is 
possibly problematic.  Then patches like this one would be much easier 
to analyze and test and keep working in the future.

This would probably require resolving 
<https://commitfest.postgresql.org/patch/5885/> first.

---

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

On 27.02.26 17:40, Jelte Fennema-Nio wrote:
> On Fri Feb 20, 2026 at 10:47 AM CET, Jelte Fennema-Nio wrote:
>> Makes total sense, I didn't realise decltype and typeof were not quite
>> 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.

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
 
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',
 )
-- 
2.53.0

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,