public inbox for [email protected]
help / color / mirror / Atom feedRe: Make copyObject work in C++
7+ messages / 3 participants
[nested] [flat]
* Re: Make copyObject work in C++
@ 2026-01-03 09:32 Jelte Fennema-Nio <[email protected]>
2026-01-10 11:09 ` Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
0 siblings, 1 reply; 7+ messages in thread
From: Jelte Fennema-Nio @ 2026-01-03 09:32 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>; Thomas Munro <[email protected]>
On Tue Dec 16, 2025 at 1:28 PM CET, Peter Eisentraut wrote:
> 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.
Attached is a patchset that does that. It required a few more fixes to
make the extension compile on MSVC too.
Attachments:
[text/x-patch] v4-0001-Make-copyObject-work-in-C.patch (2.1K, 2-v4-0001-Make-copyObject-work-in-C.patch)
download | inline diff:
From 290a1ab7788756adaed14dc2b483156d65bc3b95 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Fri, 5 Dec 2025 15:37:59 +0100
Subject: [PATCH v4 1/4] Make copyObject work 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 ++--
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/src/include/c.h b/src/include/c.h
index 77c754a904a..18bd50da510 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -418,6 +418,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
base-commit: 094b61ce3ebbb1258675cb9b4eca9198628e2177
--
2.52.0
[text/x-patch] v4-0002-Use-pg_exprtype-instead-of-typeof.patch (3.1K, 3-v4-0002-Use-pg_exprtype-instead-of-typeof.patch)
download | inline diff:
From f9693ae786b0859c627098ec9a47f048e55a1fa5 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Mon, 8 Dec 2025 08:13:51 +0100
Subject: [PATCH v4 2/4] 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 18bd50da510..beed3a1a52d 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -976,10 +976,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) \
@@ -1227,11 +1227,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
[text/x-patch] v4-0003-Revert-Replace-pg_restrict-by-standard-restrict.patch (11.3K, 4-v4-0003-Revert-Replace-pg_restrict-by-standard-restrict.patch)
download | inline diff:
From cbdc5128717aa0f2c1c0ec3592bdfb0ff09863a2 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Sat, 3 Jan 2026 00:43:59 +0100
Subject: [PATCH v4 3/4] Revert "Replace pg_restrict by standard restrict"
This reverts commit f0f2c0c1aef95757c4e7f144d5577e2b0d814279.
It turns out that this completely broke compiling C++ extensions on
MSVC. The restrict replacement with __restrict would break usages of
__decltype(restrict) in standard library headers, resulting in errors
like this:
C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt\corecrt_malloc.h(58): error C2485: '__restrict': unrecognized extended attribute
---
configure | 18 ++++++++++++++----
configure.ac | 16 ++++++++++++----
meson.build | 13 +++++++------
src/bin/pg_verifybackup/pg_verifybackup.c | 4 ++--
src/bin/pg_verifybackup/pg_verifybackup.h | 4 ++--
src/common/logging.c | 4 ++--
src/common/string.c | 2 +-
src/include/c.h | 6 ------
src/include/common/logging.h | 4 ++--
src/include/common/string.h | 2 +-
src/include/libpq/pqformat.h | 12 ++++++------
src/include/pg_config.h.in | 4 ++++
12 files changed, 53 insertions(+), 36 deletions(-)
diff --git a/configure b/configure
index 78597c6229a..26883131d4c 100755
--- a/configure
+++ b/configure
@@ -15131,10 +15131,10 @@ _ACEOF
fi
-# Even though restrict is in C99 and should be supported by all
-# supported compilers, this test is useful because it will prefer a
-# spelling that also works in C++ (often __restrict). (restrict is
-# not part of the C++ standard.)
+# MSVC doesn't cope well with defining restrict to __restrict, the
+# spelling it understands, because it conflicts with
+# __declspec(restrict). Therefore we define pg_restrict to the
+# appropriate definition, which presumably won't conflict.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C/C++ restrict keyword" >&5
$as_echo_n "checking for C/C++ restrict keyword... " >&6; }
if ${ac_cv_c_restrict+:} false; then :
@@ -15181,6 +15181,16 @@ _ACEOF
;;
esac
+if test "$ac_cv_c_restrict" = "no"; then
+ pg_restrict=""
+else
+ pg_restrict="$ac_cv_c_restrict"
+fi
+
+cat >>confdefs.h <<_ACEOF
+#define pg_restrict $pg_restrict
+_ACEOF
+
ac_fn_c_check_type "$LINENO" "struct option" "ac_cv_type_struct_option" "#ifdef HAVE_GETOPT_H
#include <getopt.h>
diff --git a/configure.ac b/configure.ac
index 2ccf410f94c..08da448c1f3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1689,11 +1689,19 @@ PGAC_UNION_SEMUN
AC_CHECK_TYPES(socklen_t, [], [], [#include <sys/socket.h>])
PGAC_STRUCT_SOCKADDR_SA_LEN
-# Even though restrict is in C99 and should be supported by all
-# supported compilers, this test is useful because it will prefer a
-# spelling that also works in C++ (often __restrict). (restrict is
-# not part of the C++ standard.)
+# MSVC doesn't cope well with defining restrict to __restrict, the
+# spelling it understands, because it conflicts with
+# __declspec(restrict). Therefore we define pg_restrict to the
+# appropriate definition, which presumably won't conflict.
AC_C_RESTRICT
+if test "$ac_cv_c_restrict" = "no"; then
+ pg_restrict=""
+else
+ pg_restrict="$ac_cv_c_restrict"
+fi
+AC_DEFINE_UNQUOTED([pg_restrict], [$pg_restrict],
+[Define to keyword to use for C99 restrict support, or to nothing if not
+supported])
AC_CHECK_TYPES([struct option], [], [],
[#ifdef HAVE_GETOPT_H
diff --git a/meson.build b/meson.build
index 467f7f005a6..cc3ded782e4 100644
--- a/meson.build
+++ b/meson.build
@@ -2825,12 +2825,13 @@ int main(void)
endforeach
-# Even though restrict is in C99 and should be supported by all
-# supported compilers, this indirection is useful because __restrict
-# also works in C++ in all supported compilers. (If not, then we
-# might have to write a real test.) (restrict is not part of the C++
-# standard.)
-cdata.set('restrict', '__restrict')
+# MSVC doesn't cope well with defining restrict to __restrict, the spelling it
+# understands, because it conflicts with __declspec(restrict). Therefore we
+# define pg_restrict to the appropriate definition, which presumably won't
+# conflict.
+#
+# We assume C99 support, so we don't need to make this conditional.
+cdata.set('pg_restrict', '__restrict')
# Most libraries are included only if they demonstrably provide a function we
diff --git a/src/bin/pg_verifybackup/pg_verifybackup.c b/src/bin/pg_verifybackup/pg_verifybackup.c
index 456e0375e13..f9f2d457f2f 100644
--- a/src/bin/pg_verifybackup/pg_verifybackup.c
+++ b/src/bin/pg_verifybackup/pg_verifybackup.c
@@ -1228,7 +1228,7 @@ parse_required_wal(verifier_context *context, char *pg_waldump_path,
* context says we should.
*/
void
-report_backup_error(verifier_context *context, const char *restrict fmt,...)
+report_backup_error(verifier_context *context, const char *pg_restrict fmt,...)
{
va_list ap;
@@ -1245,7 +1245,7 @@ report_backup_error(verifier_context *context, const char *restrict fmt,...)
* Report a fatal error and exit
*/
void
-report_fatal_error(const char *restrict fmt,...)
+report_fatal_error(const char *pg_restrict fmt,...)
{
va_list ap;
diff --git a/src/bin/pg_verifybackup/pg_verifybackup.h b/src/bin/pg_verifybackup/pg_verifybackup.h
index 1cd76a8826b..a99d9bfd581 100644
--- a/src/bin/pg_verifybackup/pg_verifybackup.h
+++ b/src/bin/pg_verifybackup/pg_verifybackup.h
@@ -96,9 +96,9 @@ typedef struct verifier_context
} verifier_context;
extern void report_backup_error(verifier_context *context,
- const char *restrict fmt,...)
+ const char *pg_restrict fmt,...)
pg_attribute_printf(2, 3);
-pg_noreturn extern void report_fatal_error(const char *restrict fmt,...)
+pg_noreturn extern void report_fatal_error(const char *pg_restrict fmt,...)
pg_attribute_printf(1, 2);
extern bool should_ignore_relpath(verifier_context *context,
const char *relpath);
diff --git a/src/common/logging.c b/src/common/logging.c
index 214a87f2189..5206949e5d8 100644
--- a/src/common/logging.c
+++ b/src/common/logging.c
@@ -206,7 +206,7 @@ pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno)
void
pg_log_generic(enum pg_log_level level, enum pg_log_part part,
- const char *restrict fmt,...)
+ const char *pg_restrict fmt,...)
{
va_list ap;
@@ -217,7 +217,7 @@ pg_log_generic(enum pg_log_level level, enum pg_log_part part,
void
pg_log_generic_v(enum pg_log_level level, enum pg_log_part part,
- const char *restrict fmt, va_list ap)
+ const char *pg_restrict fmt, va_list ap)
{
int save_errno = errno;
const char *filename = NULL;
diff --git a/src/common/string.c b/src/common/string.c
index fff14d3facf..41c74a1502d 100644
--- a/src/common/string.c
+++ b/src/common/string.c
@@ -47,7 +47,7 @@ pg_str_endswith(const char *str, const char *end)
* strtoint --- just like strtol, but returns int not long
*/
int
-strtoint(const char *restrict str, char **restrict endptr, int base)
+strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
{
long val;
diff --git a/src/include/c.h b/src/include/c.h
index beed3a1a52d..df6c753d908 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -108,12 +108,6 @@
#define inline
#endif
-/*
- * Previously used PostgreSQL-specific spelling, for backward compatibility
- * for extensions.
- */
-#define pg_restrict restrict
-
/*
* Attribute macros
*
diff --git a/src/include/common/logging.h b/src/include/common/logging.h
index b5caec0bff1..bccba4ac07b 100644
--- a/src/include/common/logging.h
+++ b/src/include/common/logging.h
@@ -93,10 +93,10 @@ void pg_logging_set_pre_callback(void (*cb) (void));
void pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno));
void pg_log_generic(enum pg_log_level level, enum pg_log_part part,
- const char *restrict fmt,...)
+ const char *pg_restrict fmt,...)
pg_attribute_printf(3, 4);
void pg_log_generic_v(enum pg_log_level level, enum pg_log_part part,
- const char *restrict fmt, va_list ap)
+ const char *pg_restrict fmt, va_list ap)
pg_attribute_printf(3, 0);
/*
diff --git a/src/include/common/string.h b/src/include/common/string.h
index 5e1bd61c710..2a7c31ea74e 100644
--- a/src/include/common/string.h
+++ b/src/include/common/string.h
@@ -25,7 +25,7 @@ typedef struct PromptInterruptContext
/* functions in src/common/string.c */
extern bool pg_str_endswith(const char *str, const char *end);
-extern int strtoint(const char *restrict str, char **restrict endptr,
+extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr,
int base);
extern char *pg_clean_ascii(const char *str, int alloc_flags);
extern int pg_strip_crlf(char *str);
diff --git a/src/include/libpq/pqformat.h b/src/include/libpq/pqformat.h
index 911d9397f5e..bc4ab1381a9 100644
--- a/src/include/libpq/pqformat.h
+++ b/src/include/libpq/pqformat.h
@@ -34,7 +34,7 @@ extern void pq_sendfloat8(StringInfo buf, float8 f);
* Append a [u]int8 to a StringInfo buffer, which already has enough space
* preallocated.
*
- * The use of restrict allows the compiler to optimize the code based on
+ * The use of pg_restrict allows the compiler to optimize the code based on
* the assumption that buf, buf->len, buf->data and *buf->data don't
* overlap. Without the annotation buf->len etc cannot be kept in a register
* over subsequent pq_writeintN calls.
@@ -43,7 +43,7 @@ extern void pq_sendfloat8(StringInfo buf, float8 f);
* overly picky and demanding a * before a restrict.
*/
static inline void
-pq_writeint8(StringInfoData *restrict buf, uint8 i)
+pq_writeint8(StringInfoData *pg_restrict buf, uint8 i)
{
uint8 ni = i;
@@ -57,7 +57,7 @@ pq_writeint8(StringInfoData *restrict buf, uint8 i)
* preallocated.
*/
static inline void
-pq_writeint16(StringInfoData *restrict buf, uint16 i)
+pq_writeint16(StringInfoData *pg_restrict buf, uint16 i)
{
uint16 ni = pg_hton16(i);
@@ -71,7 +71,7 @@ pq_writeint16(StringInfoData *restrict buf, uint16 i)
* preallocated.
*/
static inline void
-pq_writeint32(StringInfoData *restrict buf, uint32 i)
+pq_writeint32(StringInfoData *pg_restrict buf, uint32 i)
{
uint32 ni = pg_hton32(i);
@@ -85,7 +85,7 @@ pq_writeint32(StringInfoData *restrict buf, uint32 i)
* preallocated.
*/
static inline void
-pq_writeint64(StringInfoData *restrict buf, uint64 i)
+pq_writeint64(StringInfoData *pg_restrict buf, uint64 i)
{
uint64 ni = pg_hton64(i);
@@ -105,7 +105,7 @@ pq_writeint64(StringInfoData *restrict buf, uint64 i)
* sent to the frontend.
*/
static inline void
-pq_writestring(StringInfoData *restrict buf, const char *restrict str)
+pq_writestring(StringInfoData *pg_restrict buf, const char *pg_restrict str)
{
int slen = strlen(str);
char *p;
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index f0091b09cbe..bc68a815e15 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -799,6 +799,10 @@
#undef inline
#endif
+/* Define to keyword to use for C99 restrict support, or to nothing if not
+ supported */
+#undef pg_restrict
+
/* Define to the equivalent of the C99 'restrict' keyword, or to
nothing if this is not supported. Do not define if restrict is
supported directly. */
--
2.52.0
[text/x-patch] v4-0004-tests-Add-a-test-C-extension-module.patch (10.6K, 5-v4-0004-tests-Add-a-test-C-extension-module.patch)
download | inline diff:
From d58a560966d53640af8f7f24882ba004221f35ef Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Fri, 2 Jan 2026 22:31:05 +0100
Subject: [PATCH v4 4/4] tests: Add a test C++ extension module
While we already test that our headers are valid C++ using headerscheck,
it turns out that the macros we define might still expand to invalid
C++ code. This adds a small test extension that is compiled using C++
which uses a few macros that have caused problems in the passed or we
expect might cause problems in the future. This is definitely not meant
to be exhaustive. It's main purpose is to serve as a regression test for
previous failures, and as a place where future failures can easily be
added.
To get CI green, this also fixes a few issues when compiling C++
extensions on MSVC. Notably, our use of designated initializers in
common macros means that on MSVC we essentially need C++20. Given that
no-one has complained about that yet, it seems unlikely that anyone is
currently compiling C++ extensions on MSVC with a lower standard. GCC
and clang allow such initializers in earlier standards in also, even
though they only became an official part of C++20.
---
.cirrus.tasks.yml | 6 ++-
meson.build | 4 ++
src/include/c.h | 18 ++++++-
src/include/nodes/pg_list.h | 8 +--
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_cplusplusext/Makefile | 24 +++++++++
.../modules/test_cplusplusext/meson.build | 35 +++++++++++++
.../test_cplusplusext/test_cplusplusext.cpp | 51 +++++++++++++++++++
9 files changed, 140 insertions(+), 8 deletions(-)
create mode 100644 src/test/modules/test_cplusplusext/Makefile
create mode 100644 src/test/modules/test_cplusplusext/meson.build
create mode 100644 src/test/modules/test_cplusplusext/test_cplusplusext.cpp
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index 038d043d00e..94abf63265c 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -453,7 +453,8 @@ task:
# SANITIZER_FLAGS is set in the tasks below
CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
- CXXFLAGS: $CFLAGS
+ # Use -std=c++11 (not gnu++11) to catch C++ portability issues
+ CXXFLAGS: $CFLAGS -std=c++11
LDFLAGS: $SANITIZER_FLAGS
CC: ccache gcc
CXX: ccache g++
@@ -573,6 +574,7 @@ task:
su postgres <<-EOF
set -e
export CC='ccache gcc -m32'
+ export CXX='ccache g++ -m32'
meson setup \
${MESON_COMMON_PG_CONFIG_ARGS} \
--buildtype=debug \
@@ -806,7 +808,7 @@ task:
configure_script: |
vcvarsall x64
- meson setup --backend ninja %MESON_COMMON_PG_CONFIG_ARGS% --buildtype debug -Db_pch=true -Dextra_lib_dirs=c:\openssl\1.1\lib -Dextra_include_dirs=c:\openssl\1.1\include -DTAR=%TAR% %MESON_FEATURES% build
+ meson setup --backend ninja %MESON_COMMON_PG_CONFIG_ARGS% --buildtype debug -Db_pch=true -Dextra_lib_dirs=c:\openssl\1.1\lib -Dextra_include_dirs=c:\openssl\1.1\include -DTAR=%TAR% -Dcpp_args=/std:c++20 %MESON_FEATURES% build
build_script: |
vcvarsall x64
diff --git a/meson.build b/meson.build
index cc3ded782e4..4a63904870e 100644
--- a/meson.build
+++ b/meson.build
@@ -2185,6 +2185,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/c.h b/src/include/c.h
index df6c753d908..cc35530424e 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -341,6 +341,16 @@
#define pg_unreachable() abort()
#endif
+/*
+ * C++ has sligthly different syntax for inline compound literals than C. GCC
+ * and Clang support he C-style syntax too, but MSVC does not.
+ */
+#ifdef __cplusplus
+#define pg_compound_literal(type, ...) (type { __VA_ARGS__ })
+#else
+#define pg_compound_literal(type, ...) ((type) { __VA_ARGS__ })
+#endif
+
/*
* Define a compiler-independent macro for determining if an expression is a
* compile-time integer const. We don't define this macro to return 0 when
@@ -948,13 +958,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/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index ae80975548f..5498d09bcba 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -204,10 +204,10 @@ list_length(const List *l)
/*
* Convenience macros for building fixed-length lists
*/
-#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)})
+#define list_make_ptr_cell(v) pg_compound_literal(ListCell, .ptr_value = (v))
+#define list_make_int_cell(v) pg_compound_literal(ListCell, .int_value = (v))
+#define list_make_oid_cell(v) pg_compound_literal(ListCell, .oid_value = (v))
+#define list_make_xid_cell(v) pg_compound_literal(ListCell, .xid_value = (v))
#define list_make1(x1) \
list_make1_impl(T_List, list_make_ptr_cell(x1))
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 4c6d56d97d8..92ac0a342b5 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -20,6 +20,7 @@ SUBDIRS = \
test_bitmapset \
test_bloomfilter \
test_cloexec \
+ test_cplusplusext \
test_copy_callbacks \
test_custom_rmgrs \
test_custom_stats \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 1b31c5b98d6..0c7e8ad4856 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -19,6 +19,7 @@ subdir('test_aio')
subdir('test_binaryheap')
subdir('test_bitmapset')
subdir('test_bloomfilter')
+subdir('test_cplusplusext')
subdir('test_cloexec')
subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
diff --git a/src/test/modules/test_cplusplusext/Makefile b/src/test/modules/test_cplusplusext/Makefile
new file mode 100644
index 00000000000..19abd0f98c6
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/Makefile
@@ -0,0 +1,24 @@
+# src/test/modules/test_cplusplusext/Makefile
+#
+# Test that PostgreSQL headers compile with a C++ compiler.
+# If this module compiles, the test passes.
+
+MODULE_big = test_cplusplusext
+OBJS = \
+ $(WIN32RES) \
+ test_cplusplusext.o
+PGFILEDESC = "test_cplusplusext - test C++ compatibility of PostgreSQL headers"
+
+# Use C++ compiler for linking because this module includes C++ files
+override COMPILER = $(CXX) $(CXXFLAGS)
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_cplusplusext
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_cplusplusext/meson.build b/src/test/modules/test_cplusplusext/meson.build
new file mode 100644
index 00000000000..d47fbfa7f51
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/meson.build
@@ -0,0 +1,35 @@
+# Copyright (c) 2025-2026, PostgreSQL Global Development Group
+
+# This module tests that PostgreSQL headers compile with a C++ compiler.
+# It has no runtime tests - if it compiles, the test passes.
+
+if not add_languages('cpp', required: false, native: false)
+ subdir_done()
+endif
+
+cpp = meson.get_compiler('cpp')
+
+# MSVC requires C++20 for designated initializers used in PG_MODULE_MAGIC and
+# other macros. Skip if the compiler doesn't support them.
+if not cpp.compiles('''
+ struct Foo { int a; int b; };
+ Foo f = {.a = 1, .b = 2};
+ ''',
+ name: 'C++ designated initializers')
+ subdir_done()
+endif
+
+test_cplusplusext_sources = files(
+ 'test_cplusplusext.cpp',
+)
+
+if host_system == 'windows'
+ test_cplusplusext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_cplusplusext',
+ '--FILEDESC', 'test_cplusplusext - test C++ compatibility of PostgreSQL headers',])
+endif
+
+test_cplusplusext = shared_module('test_cplusplusext',
+ test_cplusplusext_sources,
+ kwargs: pg_test_mod_args,
+)
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
new file mode 100644
index 00000000000..012db8b7959
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -0,0 +1,51 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_cplusplusext.cpp
+ * Test that PostgreSQL headers compile with a C++ compiler.
+ *
+ * This file is compiled with a C++ compiler to verify that PostgreSQL
+ * headers remain compatible with C++ extensions.
+ *
+ * Copyright (c) 2025-2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+ *
+ * -------------------------------------------------------------------------
+ */
+
+extern "C" {
+#include "postgres.h"
+#include "fmgr.h"
+#include "nodes/pg_list.h"
+#include "nodes/parsenodes.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_cplusplus_compat);
+}
+
+StaticAssertDecl(sizeof(int32) == 4, "int32 should be 4 bytes");
+
+extern "C" Datum
+test_cplusplus_compat(PG_FUNCTION_ARGS)
+{
+ List *node_list = list_make1(makeNode(RangeTblRef));
+ RangeTblRef *copy = copyObject(linitial_node(RangeTblRef, node_list));
+
+ foreach_ptr(RangeTblRef, rtr, node_list) {
+ rtr->rtindex++;
+ }
+
+ foreach_node(RangeTblRef, rtr, node_list) {
+ rtr->rtindex++;
+ }
+
+ StaticAssertStmt(sizeof(int32) == 4, "int32 should be 4 bytes");
+ (void) StaticAssertExpr(sizeof(int64) == 8, "int64 should be 8 bytes");
+
+ pfree(copy);
+ list_free_deep(node_list);
+
+ PG_RETURN_VOID();
+}
--
2.52.0
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Make copyObject work in C++
2026-01-03 09:32 Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
@ 2026-01-10 11:09 ` Jelte Fennema-Nio <[email protected]>
2026-01-14 15:59 ` Re: Make copyObject work in C++ Peter Eisentraut <[email protected]>
0 siblings, 1 reply; 7+ messages in thread
From: Jelte Fennema-Nio @ 2026-01-10 11:09 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>; Thomas Munro <[email protected]>
On Sat Jan 3, 2026 at 10:32 AM CET, Jelte Fennema-Nio wrote:
> Attached is a patchset that does that. It required a few more fixes to
> make the extension compile on MSVC too.
Rebased after Peter merged the C++ improvements from the other thread.
Attachments:
[text/x-patch] v5-0001-Make-copyObject-work-in-C.patch (2.1K, 2-v5-0001-Make-copyObject-work-in-C.patch)
download | inline diff:
From b46b6d54553e008db803bbc3ceeac4892eab099c Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Fri, 5 Dec 2025 15:37:59 +0100
Subject: [PATCH v5 1/4] Make copyObject work 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 ++--
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/src/include/c.h b/src/include/c.h
index 335af3352d1..b17cc1203b1 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -418,6 +418,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
base-commit: e5a5e0a90750d665cab417322b9f85c806430d85
--
2.52.0
[text/x-patch] v5-0002-Use-pg_exprtype-instead-of-typeof.patch (3.1K, 3-v5-0002-Use-pg_exprtype-instead-of-typeof.patch)
download | inline diff:
From 740895e8b8fb67e2418d9cceb407baa8056814b8 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Mon, 8 Dec 2025 08:13:51 +0100
Subject: [PATCH v5 2/4] 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 b17cc1203b1..39105671e6c 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -984,10 +984,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) \
@@ -1235,11 +1235,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
[text/x-patch] v5-0003-Revert-Replace-pg_restrict-by-standard-restrict.patch (11.3K, 4-v5-0003-Revert-Replace-pg_restrict-by-standard-restrict.patch)
download | inline diff:
From 5fbc7dc89d309f7094a81e77703ec0b5339be7f7 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Sat, 3 Jan 2026 00:43:59 +0100
Subject: [PATCH v5 3/4] Revert "Replace pg_restrict by standard restrict"
This reverts commit f0f2c0c1aef95757c4e7f144d5577e2b0d814279.
It turns out that this completely broke compiling C++ extensions on
MSVC. The restrict replacement with __restrict would break usages of
__decltype(restrict) in standard library headers, resulting in errors
like this:
C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt\corecrt_malloc.h(58): error C2485: '__restrict': unrecognized extended attribute
---
configure | 18 ++++++++++++++----
configure.ac | 16 ++++++++++++----
meson.build | 13 +++++++------
src/bin/pg_verifybackup/pg_verifybackup.c | 4 ++--
src/bin/pg_verifybackup/pg_verifybackup.h | 4 ++--
src/common/logging.c | 4 ++--
src/common/string.c | 2 +-
src/include/c.h | 6 ------
src/include/common/logging.h | 4 ++--
src/include/common/string.h | 2 +-
src/include/libpq/pqformat.h | 12 ++++++------
src/include/pg_config.h.in | 4 ++++
12 files changed, 53 insertions(+), 36 deletions(-)
diff --git a/configure b/configure
index 045c913865d..575f4f05245 100755
--- a/configure
+++ b/configure
@@ -15131,10 +15131,10 @@ _ACEOF
fi
-# Even though restrict is in C99 and should be supported by all
-# supported compilers, this test is useful because it will prefer a
-# spelling that also works in C++ (often __restrict). (restrict is
-# not part of the C++ standard.)
+# MSVC doesn't cope well with defining restrict to __restrict, the
+# spelling it understands, because it conflicts with
+# __declspec(restrict). Therefore we define pg_restrict to the
+# appropriate definition, which presumably won't conflict.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C/C++ restrict keyword" >&5
$as_echo_n "checking for C/C++ restrict keyword... " >&6; }
if ${ac_cv_c_restrict+:} false; then :
@@ -15181,6 +15181,16 @@ _ACEOF
;;
esac
+if test "$ac_cv_c_restrict" = "no"; then
+ pg_restrict=""
+else
+ pg_restrict="$ac_cv_c_restrict"
+fi
+
+cat >>confdefs.h <<_ACEOF
+#define pg_restrict $pg_restrict
+_ACEOF
+
ac_fn_c_check_type "$LINENO" "struct option" "ac_cv_type_struct_option" "#ifdef HAVE_GETOPT_H
#include <getopt.h>
diff --git a/configure.ac b/configure.ac
index 145197e6bd6..c77c2639ec1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1687,11 +1687,19 @@ PGAC_UNION_SEMUN
AC_CHECK_TYPES(socklen_t, [], [], [#include <sys/socket.h>])
PGAC_STRUCT_SOCKADDR_SA_LEN
-# Even though restrict is in C99 and should be supported by all
-# supported compilers, this test is useful because it will prefer a
-# spelling that also works in C++ (often __restrict). (restrict is
-# not part of the C++ standard.)
+# MSVC doesn't cope well with defining restrict to __restrict, the
+# spelling it understands, because it conflicts with
+# __declspec(restrict). Therefore we define pg_restrict to the
+# appropriate definition, which presumably won't conflict.
AC_C_RESTRICT
+if test "$ac_cv_c_restrict" = "no"; then
+ pg_restrict=""
+else
+ pg_restrict="$ac_cv_c_restrict"
+fi
+AC_DEFINE_UNQUOTED([pg_restrict], [$pg_restrict],
+[Define to keyword to use for C99 restrict support, or to nothing if not
+supported])
AC_CHECK_TYPES([struct option], [], [],
[#ifdef HAVE_GETOPT_H
diff --git a/meson.build b/meson.build
index 555c94796c6..55df1dd3797 100644
--- a/meson.build
+++ b/meson.build
@@ -2833,12 +2833,13 @@ int main(void)
endforeach
-# Even though restrict is in C99 and should be supported by all
-# supported compilers, this indirection is useful because __restrict
-# also works in C++ in all supported compilers. (If not, then we
-# might have to write a real test.) (restrict is not part of the C++
-# standard.)
-cdata.set('restrict', '__restrict')
+# MSVC doesn't cope well with defining restrict to __restrict, the spelling it
+# understands, because it conflicts with __declspec(restrict). Therefore we
+# define pg_restrict to the appropriate definition, which presumably won't
+# conflict.
+#
+# We assume C99 support, so we don't need to make this conditional.
+cdata.set('pg_restrict', '__restrict')
# Most libraries are included only if they demonstrably provide a function we
diff --git a/src/bin/pg_verifybackup/pg_verifybackup.c b/src/bin/pg_verifybackup/pg_verifybackup.c
index 456e0375e13..f9f2d457f2f 100644
--- a/src/bin/pg_verifybackup/pg_verifybackup.c
+++ b/src/bin/pg_verifybackup/pg_verifybackup.c
@@ -1228,7 +1228,7 @@ parse_required_wal(verifier_context *context, char *pg_waldump_path,
* context says we should.
*/
void
-report_backup_error(verifier_context *context, const char *restrict fmt,...)
+report_backup_error(verifier_context *context, const char *pg_restrict fmt,...)
{
va_list ap;
@@ -1245,7 +1245,7 @@ report_backup_error(verifier_context *context, const char *restrict fmt,...)
* Report a fatal error and exit
*/
void
-report_fatal_error(const char *restrict fmt,...)
+report_fatal_error(const char *pg_restrict fmt,...)
{
va_list ap;
diff --git a/src/bin/pg_verifybackup/pg_verifybackup.h b/src/bin/pg_verifybackup/pg_verifybackup.h
index 1cd76a8826b..a99d9bfd581 100644
--- a/src/bin/pg_verifybackup/pg_verifybackup.h
+++ b/src/bin/pg_verifybackup/pg_verifybackup.h
@@ -96,9 +96,9 @@ typedef struct verifier_context
} verifier_context;
extern void report_backup_error(verifier_context *context,
- const char *restrict fmt,...)
+ const char *pg_restrict fmt,...)
pg_attribute_printf(2, 3);
-pg_noreturn extern void report_fatal_error(const char *restrict fmt,...)
+pg_noreturn extern void report_fatal_error(const char *pg_restrict fmt,...)
pg_attribute_printf(1, 2);
extern bool should_ignore_relpath(verifier_context *context,
const char *relpath);
diff --git a/src/common/logging.c b/src/common/logging.c
index 214a87f2189..5206949e5d8 100644
--- a/src/common/logging.c
+++ b/src/common/logging.c
@@ -206,7 +206,7 @@ pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno)
void
pg_log_generic(enum pg_log_level level, enum pg_log_part part,
- const char *restrict fmt,...)
+ const char *pg_restrict fmt,...)
{
va_list ap;
@@ -217,7 +217,7 @@ pg_log_generic(enum pg_log_level level, enum pg_log_part part,
void
pg_log_generic_v(enum pg_log_level level, enum pg_log_part part,
- const char *restrict fmt, va_list ap)
+ const char *pg_restrict fmt, va_list ap)
{
int save_errno = errno;
const char *filename = NULL;
diff --git a/src/common/string.c b/src/common/string.c
index fff14d3facf..41c74a1502d 100644
--- a/src/common/string.c
+++ b/src/common/string.c
@@ -47,7 +47,7 @@ pg_str_endswith(const char *str, const char *end)
* strtoint --- just like strtol, but returns int not long
*/
int
-strtoint(const char *restrict str, char **restrict endptr, int base)
+strtoint(const char *pg_restrict str, char **pg_restrict endptr, int base)
{
long val;
diff --git a/src/include/c.h b/src/include/c.h
index 39105671e6c..65173b9d9fd 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -108,12 +108,6 @@
#define inline
#endif
-/*
- * Previously used PostgreSQL-specific spelling, for backward compatibility
- * for extensions.
- */
-#define pg_restrict restrict
-
/*
* Attribute macros
*
diff --git a/src/include/common/logging.h b/src/include/common/logging.h
index b5caec0bff1..bccba4ac07b 100644
--- a/src/include/common/logging.h
+++ b/src/include/common/logging.h
@@ -93,10 +93,10 @@ void pg_logging_set_pre_callback(void (*cb) (void));
void pg_logging_set_locus_callback(void (*cb) (const char **filename, uint64 *lineno));
void pg_log_generic(enum pg_log_level level, enum pg_log_part part,
- const char *restrict fmt,...)
+ const char *pg_restrict fmt,...)
pg_attribute_printf(3, 4);
void pg_log_generic_v(enum pg_log_level level, enum pg_log_part part,
- const char *restrict fmt, va_list ap)
+ const char *pg_restrict fmt, va_list ap)
pg_attribute_printf(3, 0);
/*
diff --git a/src/include/common/string.h b/src/include/common/string.h
index 5e1bd61c710..2a7c31ea74e 100644
--- a/src/include/common/string.h
+++ b/src/include/common/string.h
@@ -25,7 +25,7 @@ typedef struct PromptInterruptContext
/* functions in src/common/string.c */
extern bool pg_str_endswith(const char *str, const char *end);
-extern int strtoint(const char *restrict str, char **restrict endptr,
+extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr,
int base);
extern char *pg_clean_ascii(const char *str, int alloc_flags);
extern int pg_strip_crlf(char *str);
diff --git a/src/include/libpq/pqformat.h b/src/include/libpq/pqformat.h
index 911d9397f5e..bc4ab1381a9 100644
--- a/src/include/libpq/pqformat.h
+++ b/src/include/libpq/pqformat.h
@@ -34,7 +34,7 @@ extern void pq_sendfloat8(StringInfo buf, float8 f);
* Append a [u]int8 to a StringInfo buffer, which already has enough space
* preallocated.
*
- * The use of restrict allows the compiler to optimize the code based on
+ * The use of pg_restrict allows the compiler to optimize the code based on
* the assumption that buf, buf->len, buf->data and *buf->data don't
* overlap. Without the annotation buf->len etc cannot be kept in a register
* over subsequent pq_writeintN calls.
@@ -43,7 +43,7 @@ extern void pq_sendfloat8(StringInfo buf, float8 f);
* overly picky and demanding a * before a restrict.
*/
static inline void
-pq_writeint8(StringInfoData *restrict buf, uint8 i)
+pq_writeint8(StringInfoData *pg_restrict buf, uint8 i)
{
uint8 ni = i;
@@ -57,7 +57,7 @@ pq_writeint8(StringInfoData *restrict buf, uint8 i)
* preallocated.
*/
static inline void
-pq_writeint16(StringInfoData *restrict buf, uint16 i)
+pq_writeint16(StringInfoData *pg_restrict buf, uint16 i)
{
uint16 ni = pg_hton16(i);
@@ -71,7 +71,7 @@ pq_writeint16(StringInfoData *restrict buf, uint16 i)
* preallocated.
*/
static inline void
-pq_writeint32(StringInfoData *restrict buf, uint32 i)
+pq_writeint32(StringInfoData *pg_restrict buf, uint32 i)
{
uint32 ni = pg_hton32(i);
@@ -85,7 +85,7 @@ pq_writeint32(StringInfoData *restrict buf, uint32 i)
* preallocated.
*/
static inline void
-pq_writeint64(StringInfoData *restrict buf, uint64 i)
+pq_writeint64(StringInfoData *pg_restrict buf, uint64 i)
{
uint64 ni = pg_hton64(i);
@@ -105,7 +105,7 @@ pq_writeint64(StringInfoData *restrict buf, uint64 i)
* sent to the frontend.
*/
static inline void
-pq_writestring(StringInfoData *restrict buf, const char *restrict str)
+pq_writestring(StringInfoData *pg_restrict buf, const char *pg_restrict str)
{
int slen = strlen(str);
char *p;
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 10fa85e78c2..339268dc8ef 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -786,6 +786,10 @@
#undef inline
#endif
+/* Define to keyword to use for C99 restrict support, or to nothing if not
+ supported */
+#undef pg_restrict
+
/* Define to the equivalent of the C99 'restrict' keyword, or to
nothing if this is not supported. Do not define if restrict is
supported directly. */
--
2.52.0
[text/x-patch] v5-0004-tests-Add-a-test-C-extension-module.patch (10.6K, 5-v5-0004-tests-Add-a-test-C-extension-module.patch)
download | inline diff:
From f0d4b25e67555cd7f43cb17ab7bcff870bfea669 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Fri, 2 Jan 2026 22:31:05 +0100
Subject: [PATCH v5 4/4] tests: Add a test C++ extension module
While we already test that our headers are valid C++ using headerscheck,
it turns out that the macros we define might still expand to invalid
C++ code. This adds a small test extension that is compiled using C++
which uses a few macros that have caused problems in the passed or we
expect might cause problems in the future. This is definitely not meant
to be exhaustive. It's main purpose is to serve as a regression test for
previous failures, and as a place where future failures can easily be
added.
To get CI green, this also fixes a few issues when compiling C++
extensions on MSVC. Notably, our use of designated initializers in
common macros means that on MSVC we essentially need C++20. Given that
no-one has complained about that yet, it seems unlikely that anyone is
currently compiling C++ extensions on MSVC with a lower standard. GCC
and clang allow such initializers when compiling for older C++
standards, even though they only became an official part of C++20.
---
.cirrus.tasks.yml | 7 ++-
meson.build | 4 ++
src/include/c.h | 18 ++++++-
src/include/nodes/pg_list.h | 8 +--
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_cplusplusext/Makefile | 24 +++++++++
.../modules/test_cplusplusext/meson.build | 35 +++++++++++++
.../test_cplusplusext/test_cplusplusext.cpp | 51 +++++++++++++++++++
9 files changed, 141 insertions(+), 8 deletions(-)
create mode 100644 src/test/modules/test_cplusplusext/Makefile
create mode 100644 src/test/modules/test_cplusplusext/meson.build
create mode 100644 src/test/modules/test_cplusplusext/test_cplusplusext.cpp
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index 2a821593ce5..c4b4d213b68 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -450,7 +450,8 @@ task:
# SANITIZER_FLAGS is set in the tasks below
CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
- CXXFLAGS: $CFLAGS
+ # Use -std=c++11 (not gnu++11) to catch C++ portability issues
+ CXXFLAGS: $CFLAGS -std=c++11
LDFLAGS: $SANITIZER_FLAGS
CC: ccache gcc
CXX: ccache g++
@@ -802,9 +803,11 @@ task:
echo 127.0.0.3 pg-loadbalancetest >> c:\Windows\System32\Drivers\etc\hosts
type c:\Windows\System32\Drivers\etc\hosts
+ # Uses C++20 standard, because that's needed for MSVC to accept designated
+ # initializers.
configure_script: |
vcvarsall x64
- meson setup --backend ninja %MESON_COMMON_PG_CONFIG_ARGS% --buildtype debug -Db_pch=true -Dextra_lib_dirs=c:\openssl\1.1\lib -Dextra_include_dirs=c:\openssl\1.1\include -DTAR=%TAR% %MESON_FEATURES% build
+ meson setup --backend ninja %MESON_COMMON_PG_CONFIG_ARGS% --buildtype debug -Db_pch=true -Dextra_lib_dirs=c:\openssl\1.1\lib -Dextra_include_dirs=c:\openssl\1.1\include -DTAR=%TAR% -Dcpp_args=/std:c++20 %MESON_FEATURES% build
build_script: |
vcvarsall x64
diff --git a/meson.build b/meson.build
index 55df1dd3797..63d725dfc9d 100644
--- a/meson.build
+++ b/meson.build
@@ -2192,6 +2192,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/c.h b/src/include/c.h
index 65173b9d9fd..d2f354e7e15 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -341,6 +341,16 @@
#define pg_unreachable() abort()
#endif
+/*
+ * C++ has sligthly different syntax for inline compound literals than C. GCC
+ * and Clang support he C-style syntax too, but MSVC does not.
+ */
+#ifdef __cplusplus
+#define pg_compound_literal(type, ...) (type { __VA_ARGS__ })
+#else
+#define pg_compound_literal(type, ...) ((type) { __VA_ARGS__ })
+#endif
+
/*
* Define a compiler-independent macro for determining if an expression is a
* compile-time integer const. We don't define this macro to return 0 when
@@ -956,13 +966,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/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index ae80975548f..5498d09bcba 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -204,10 +204,10 @@ list_length(const List *l)
/*
* Convenience macros for building fixed-length lists
*/
-#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)})
+#define list_make_ptr_cell(v) pg_compound_literal(ListCell, .ptr_value = (v))
+#define list_make_int_cell(v) pg_compound_literal(ListCell, .int_value = (v))
+#define list_make_oid_cell(v) pg_compound_literal(ListCell, .oid_value = (v))
+#define list_make_xid_cell(v) pg_compound_literal(ListCell, .xid_value = (v))
#define list_make1(x1) \
list_make1_impl(T_List, list_make_ptr_cell(x1))
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 4c6d56d97d8..92ac0a342b5 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -20,6 +20,7 @@ SUBDIRS = \
test_bitmapset \
test_bloomfilter \
test_cloexec \
+ test_cplusplusext \
test_copy_callbacks \
test_custom_rmgrs \
test_custom_stats \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 1b31c5b98d6..0c7e8ad4856 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -19,6 +19,7 @@ subdir('test_aio')
subdir('test_binaryheap')
subdir('test_bitmapset')
subdir('test_bloomfilter')
+subdir('test_cplusplusext')
subdir('test_cloexec')
subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
diff --git a/src/test/modules/test_cplusplusext/Makefile b/src/test/modules/test_cplusplusext/Makefile
new file mode 100644
index 00000000000..19abd0f98c6
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/Makefile
@@ -0,0 +1,24 @@
+# src/test/modules/test_cplusplusext/Makefile
+#
+# Test that PostgreSQL headers compile with a C++ compiler.
+# If this module compiles, the test passes.
+
+MODULE_big = test_cplusplusext
+OBJS = \
+ $(WIN32RES) \
+ test_cplusplusext.o
+PGFILEDESC = "test_cplusplusext - test C++ compatibility of PostgreSQL headers"
+
+# Use C++ compiler for linking because this module includes C++ files
+override COMPILER = $(CXX) $(CXXFLAGS)
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_cplusplusext
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_cplusplusext/meson.build b/src/test/modules/test_cplusplusext/meson.build
new file mode 100644
index 00000000000..d47fbfa7f51
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/meson.build
@@ -0,0 +1,35 @@
+# Copyright (c) 2025-2026, PostgreSQL Global Development Group
+
+# This module tests that PostgreSQL headers compile with a C++ compiler.
+# It has no runtime tests - if it compiles, the test passes.
+
+if not add_languages('cpp', required: false, native: false)
+ subdir_done()
+endif
+
+cpp = meson.get_compiler('cpp')
+
+# MSVC requires C++20 for designated initializers used in PG_MODULE_MAGIC and
+# other macros. Skip if the compiler doesn't support them.
+if not cpp.compiles('''
+ struct Foo { int a; int b; };
+ Foo f = {.a = 1, .b = 2};
+ ''',
+ name: 'C++ designated initializers')
+ subdir_done()
+endif
+
+test_cplusplusext_sources = files(
+ 'test_cplusplusext.cpp',
+)
+
+if host_system == 'windows'
+ test_cplusplusext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_cplusplusext',
+ '--FILEDESC', 'test_cplusplusext - test C++ compatibility of PostgreSQL headers',])
+endif
+
+test_cplusplusext = shared_module('test_cplusplusext',
+ test_cplusplusext_sources,
+ kwargs: pg_test_mod_args,
+)
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
new file mode 100644
index 00000000000..012db8b7959
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -0,0 +1,51 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_cplusplusext.cpp
+ * Test that PostgreSQL headers compile with a C++ compiler.
+ *
+ * This file is compiled with a C++ compiler to verify that PostgreSQL
+ * headers remain compatible with C++ extensions.
+ *
+ * Copyright (c) 2025-2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+ *
+ * -------------------------------------------------------------------------
+ */
+
+extern "C" {
+#include "postgres.h"
+#include "fmgr.h"
+#include "nodes/pg_list.h"
+#include "nodes/parsenodes.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(test_cplusplus_compat);
+}
+
+StaticAssertDecl(sizeof(int32) == 4, "int32 should be 4 bytes");
+
+extern "C" Datum
+test_cplusplus_compat(PG_FUNCTION_ARGS)
+{
+ List *node_list = list_make1(makeNode(RangeTblRef));
+ RangeTblRef *copy = copyObject(linitial_node(RangeTblRef, node_list));
+
+ foreach_ptr(RangeTblRef, rtr, node_list) {
+ rtr->rtindex++;
+ }
+
+ foreach_node(RangeTblRef, rtr, node_list) {
+ rtr->rtindex++;
+ }
+
+ StaticAssertStmt(sizeof(int32) == 4, "int32 should be 4 bytes");
+ (void) StaticAssertExpr(sizeof(int64) == 8, "int64 should be 8 bytes");
+
+ pfree(copy);
+ list_free_deep(node_list);
+
+ PG_RETURN_VOID();
+}
--
2.52.0
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Make copyObject work in C++
2026-01-03 09:32 Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-10 11:09 ` Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
@ 2026-01-14 15:59 ` Peter Eisentraut <[email protected]>
2026-01-17 15:25 ` Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
0 siblings, 1 reply; 7+ messages in thread
From: Peter Eisentraut @ 2026-01-14 15:59 UTC (permalink / raw)
To: Jelte Fennema-Nio <[email protected]>; PostgreSQL Hackers <[email protected]>; Thomas Munro <[email protected]>
On 10.01.26 12:09, Jelte Fennema-Nio wrote:
> On Sat Jan 3, 2026 at 10:32 AM CET, Jelte Fennema-Nio wrote:
>> Attached is a patchset that does that. It required a few more fixes to
>> make the extension compile on MSVC too.
>
> Rebased after Peter merged the C++ improvements from the other thread.
I have a couple of comments on the sample extension module.
I think this module should have a runtime test, too. Otherwise you
don't know that you got the linkage correct, or whether this works at
all. It doesn't have to do much, like it could literally be a + b, and
it could evolve in the future to test hooks, _PG_init, etc.
Let's put a README file in the module's directory instead of putting the
explanation into the Makefile/meson.build.
I wonder if the module's build integration would work correctly in the
autoconf/makefile case if no C++ is available. AFAICT, it would fail to
build with g++ not found or similar.
AFAICT, the minimum changes to get a minimum test module to work are
- fix for "restrict", recently committed
- disable warning about zero-length arrays, seems trivial
- named designated initializers
I learned that named designated initializers in C++ are not allowed to
be specified out of order, so they are not a full equivalent to the C
syntax. This could be a problem for example if someone wanted in the
future to have something like
PG_MODULE_MAGIC_EXT(.threads_supported = true)
(while not specifying the leading .name and .version fields).
I think for now the easiest fix would be to just not use the named
initializers in the definition of PG_MODULE_MAGIC_DATA. Then we don't
need to require C++20 and have that additional code. In the future, we
might need a different solution more suitable for C++.
The use of -std=c++11 for CI is a valid idea; I have often wanted that
for C as well. But conversely we also want to allow testing optional
extension and future C standard features. So we need a comprehensive
solution there that covers both ends and both languages. Let's leave
that out for now and think about it separately.
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Make copyObject work in C++
2026-01-03 09:32 Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-10 11:09 ` Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-14 15:59 ` Re: Make copyObject work in C++ Peter Eisentraut <[email protected]>
@ 2026-01-17 15:25 ` Jelte Fennema-Nio <[email protected]>
2026-01-20 16:28 ` Re: Make copyObject work in C++ Peter Eisentraut <[email protected]>
0 siblings, 1 reply; 7+ messages in thread
From: Jelte Fennema-Nio @ 2026-01-17 15:25 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; Thomas Munro <[email protected]>
On Wed, 14 Jan 2026 at 16:59, Peter Eisentraut <[email protected]> wrote:
> I think this module should have a runtime test, too. Otherwise you
> don't know that you got the linkage correct, or whether this works at
> all. It doesn't have to do much, like it could literally be a + b, and
> it could evolve in the future to test hooks, _PG_init, etc.
Done
> Let's put a README file in the module's directory instead of putting the
> explanation into the Makefile/meson.build.
Done
> I wonder if the module's build integration would work correctly in the
> autoconf/makefile case if no C++ is available. AFAICT, it would fail to
> build with g++ not found or similar.
Changed this to check that if CXX is g++, the command is also actually
available before including the module for the build.
> AFAICT, the minimum changes to get a minimum test module to work are
>
> - fix for "restrict", recently committed
> - disable warning about zero-length arrays, seems trivial
> - named designated initializers
Correct, I've now restructured the commits to have the module
introduction as the first one. Then all the other commits, both fix a
macro to work in C++ and add some usage of those macros as coverage to
the previously added module.
> I learned that named designated initializers in C++ are not allowed to
> be specified out of order, so they are not a full equivalent to the C
> syntax. This could be a problem for example if someone wanted in the
> future to have something like
>
> PG_MODULE_MAGIC_EXT(.threads_supported = true)
>
> (while not specifying the leading .name and .version fields).
This would still work fine, because fields are left out. The problem
occurs when defining values for fields, but in a different order than
the fields are specified in the struct (so e.g. by putting .version
before .name). The reason for that is related to destructor ordering.
> I think for now the easiest fix would be to just not use the named
> initializers in the definition of PG_MODULE_MAGIC_DATA. Then we don't
> need to require C++20 and have that additional code. In the future, we
> might need a different solution more suitable for C++.
There is a small problem with this though. Using both designated an
non-designated initializers, is not valid standard C++. I even get a
warning (but no error) about that with clang:
../src/test/modules/test_cplusplusext/test_cplusplusext.cpp:24:2: warning: mixture of designated and non-designated initializers in the same initializer list is a C99 extension [-Wc99-designator]
In C mixing them is allowed just fine.
Sadly that means that C++ extensions (even when compiled for C++20)
shouldn't use PG_MODULE_MAGIC_EXT with designated initializers at all.
I've updated the documentation to reflect that. I agree that it's better
to avoid requiring C++20 on MSVC (at least for now).
> The use of -std=c++11 for CI is a valid idea; I have often wanted that
> for C as well. But conversely we also want to allow testing optional
> extension and future C standard features. So we need a comprehensive
> solution there that covers both ends and both languages. Let's leave
> that out for now and think about it separately.
Removed
Attachments:
[text/x-patch] v6-0001-tests-Add-a-test-C-extension-module.patch (12.1K, 2-v6-0001-tests-Add-a-test-C-extension-module.patch)
download | inline diff:
From 6ece40b64ad1dcf44746680b07618ee783dd7e3b Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Fri, 2 Jan 2026 22:31:05 +0100
Subject: [PATCH v6 1/5] tests: Add a test C++ extension module
While we already test that our headers are valid C++ using headerscheck,
it turns out that the macros we define might still expand to invalid
C++ code. This adds a minimal test extension that is compiled using C++
to test that it's actually possible to build and run extensions written
in C++. Future commits will improve C++ compatibility of some of our
macros and add usage of them to this extension make sure that they don't
regress in the future.
To get CI green, this also fixes an issue when compiling C++ extensions
on MSVC: Our use of designated initializers in PG_MODULE_MAGIC_DATA
caused it to only be possible. This reverts to using positional
initializers instead. 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.
---
doc/src/sgml/xfunc.sgml | 6 +++
meson.build | 4 ++
src/include/fmgr.h | 12 +++---
src/test/modules/Makefile | 11 ++++++
src/test/modules/meson.build | 1 +
src/test/modules/test_cplusplusext/.gitignore | 3 ++
src/test/modules/test_cplusplusext/Makefile | 26 +++++++++++++
src/test/modules/test_cplusplusext/README | 10 +++++
.../expected/test_cplusplusext.out | 14 +++++++
.../modules/test_cplusplusext/meson.build | 37 +++++++++++++++++++
.../sql/test_cplusplusext.sql | 6 +++
.../test_cplusplusext--1.0.sql | 8 ++++
.../test_cplusplusext.control | 4 ++
.../test_cplusplusext/test_cplusplusext.cpp | 37 +++++++++++++++++++
14 files changed, 174 insertions(+), 5 deletions(-)
create mode 100644 src/test/modules/test_cplusplusext/.gitignore
create mode 100644 src/test/modules/test_cplusplusext/Makefile
create mode 100644 src/test/modules/test_cplusplusext/README
create mode 100644 src/test/modules/test_cplusplusext/expected/test_cplusplusext.out
create mode 100644 src/test/modules/test_cplusplusext/meson.build
create mode 100644 src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql
create mode 100644 src/test/modules/test_cplusplusext/test_cplusplusext--1.0.sql
create mode 100644 src/test/modules/test_cplusplusext/test_cplusplusext.control
create mode 100644 src/test/modules/test_cplusplusext/test_cplusplusext.cpp
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 6d304f32fb0..aef791da7c3 100644
--- a/meson.build
+++ b/meson.build
@@ -2211,6 +2211,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/Makefile b/src/test/modules/Makefile
index 4c6d56d97d8..c90b1bd0dc0 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -75,5 +75,16 @@ else
ALWAYS_SUBDIRS += ldap_password_func
endif
+# test_cplusplusext requires a working C++ compiler. If CXX is set to g++, then
+# that could be because g++ is AC_PROG_CXX's fallback when no C++ compiler is
+# found. So in that case we do check to see if it actually exists.
+ifneq ($(CXX),g++)
+SUBDIRS += test_cplusplusext
+else ifneq ($(shell command -v $(CXX)),)
+SUBDIRS += test_cplusplusext
+else
+ALWAYS_SUBDIRS += test_cplusplusext
+endif
+
$(recurse)
$(recurse_always)
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 1b31c5b98d6..0c7e8ad4856 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -19,6 +19,7 @@ subdir('test_aio')
subdir('test_binaryheap')
subdir('test_bitmapset')
subdir('test_bloomfilter')
+subdir('test_cplusplusext')
subdir('test_cloexec')
subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
diff --git a/src/test/modules/test_cplusplusext/.gitignore b/src/test/modules/test_cplusplusext/.gitignore
new file mode 100644
index 00000000000..913175ff6e6
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/.gitignore
@@ -0,0 +1,3 @@
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_cplusplusext/Makefile b/src/test/modules/test_cplusplusext/Makefile
new file mode 100644
index 00000000000..88cd4403823
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/Makefile
@@ -0,0 +1,26 @@
+# src/test/modules/test_cplusplusext/Makefile
+
+MODULE_big = test_cplusplusext
+OBJS = \
+ $(WIN32RES) \
+ test_cplusplusext.o
+PGFILEDESC = "test_cplusplusext - test C++ compatibility of PostgreSQL headers"
+
+EXTENSION = test_cplusplusext
+DATA = test_cplusplusext--1.0.sql
+
+REGRESS = test_cplusplusext
+
+# Use C++ compiler for linking because this module includes C++ files
+override COMPILER = $(CXX) $(CXXFLAGS)
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_cplusplusext
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_cplusplusext/README b/src/test/modules/test_cplusplusext/README
new file mode 100644
index 00000000000..c7a54fe8e7f
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/README
@@ -0,0 +1,10 @@
+test_cplusplusext - Test C++ Extension Compatibility
+====================================================
+
+This test module verifies that PostgreSQL headers and macros work correctly
+when compiled with a C++ compiler.
+
+While PostgreSQL already tests that headers are syntactically valid C++ using
+headerscheck, the macros defined in those headers might still expand to invalid
+C++ code. This module catches such issues by actually compiling and running an
+extension that's written in C++.
diff --git a/src/test/modules/test_cplusplusext/expected/test_cplusplusext.out b/src/test/modules/test_cplusplusext/expected/test_cplusplusext.out
new file mode 100644
index 00000000000..25600cfd1b4
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/expected/test_cplusplusext.out
@@ -0,0 +1,14 @@
+CREATE EXTENSION test_cplusplusext;
+SELECT test_cplusplus_add(1, 2);
+ test_cplusplus_add
+--------------------
+ 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
new file mode 100644
index 00000000000..0ddb67978ef
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/meson.build
@@ -0,0 +1,37 @@
+# Copyright (c) 2025-2026, PostgreSQL Global Development Group
+
+if not add_languages('cpp', required: false, native: false)
+ subdir_done()
+endif
+
+test_cplusplusext_sources = files(
+ 'test_cplusplusext.cpp',
+)
+
+if host_system == 'windows'
+ test_cplusplusext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_cplusplusext',
+ '--FILEDESC', 'test_cplusplusext - test C++ compatibility of PostgreSQL headers',])
+endif
+
+test_cplusplusext = shared_module('test_cplusplusext',
+ test_cplusplusext_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_cplusplusext
+
+test_install_data += files(
+ 'test_cplusplusext.control',
+ 'test_cplusplusext--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_cplusplusext',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_cplusplusext',
+ ],
+ },
+}
diff --git a/src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql b/src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql
new file mode 100644
index 00000000000..693910ba637
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql
@@ -0,0 +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--1.0.sql b/src/test/modules/test_cplusplusext/test_cplusplusext--1.0.sql
new file mode 100644
index 00000000000..c54acb76823
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext--1.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_cplusplusext/test_cplusplusext--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_cplusplusext" to load this file. \quit
+
+CREATE FUNCTION test_cplusplus_add(int4, int4) RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.control b/src/test/modules/test_cplusplusext/test_cplusplusext.control
new file mode 100644
index 00000000000..640a0a51f35
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.control
@@ -0,0 +1,4 @@
+comment = 'Test module for C++ extension compatibility'
+default_version = '1.0'
+module_pathname = '$libdir/test_cplusplusext'
+relocatable = true
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
new file mode 100644
index 00000000000..7108e5b1cc5
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -0,0 +1,37 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_cplusplusext.cpp
+ * Test that PostgreSQL headers compile with a C++ compiler.
+ *
+ * This file is compiled with a C++ compiler to verify that PostgreSQL
+ * headers remain compatible with C++ extensions.
+ *
+ * Copyright (c) 2025-2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+ *
+ * -------------------------------------------------------------------------
+ */
+
+extern "C" {
+#include "postgres.h"
+#include "fmgr.h"
+
+PG_MODULE_MAGIC_EXT("test_cplusplusext", "1.2");
+
+PG_FUNCTION_INFO_V1(test_cplusplus_add);
+}
+
+/*
+ * Simple function that returns the sum of two integers. This verifies that
+ * C++ extension modules can be loaded and called correctly at runtime.
+ */
+extern "C" Datum
+test_cplusplus_add(PG_FUNCTION_ARGS)
+{
+ int32 a = PG_GETARG_INT32(0);
+ int32 b = PG_GETARG_INT32(1);
+
+ PG_RETURN_INT32(a + b);
+}
base-commit: 6831cd9e3b082d7b830c3196742dd49e3540c49b
--
2.52.0
[text/x-patch] v6-0002-Support-using-copyObject-in-C.patch (2.9K, 3-v6-0002-Support-using-copyObject-in-C.patch)
download | inline diff:
From 923e443ff3724188ba13c93337b07378e4860d9a Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Fri, 5 Dec 2025 15:37:59 +0100
Subject: [PATCH v6 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 7136102e5ff..a0b1261f7d3 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
[text/x-patch] v6-0003-Use-pg_exprtype-instead-of-typeof.patch (3.1K, 4-v6-0003-Use-pg_exprtype-instead-of-typeof.patch)
download | inline diff:
From ac5cbcfc21a2b91f646fbd636e94c79c5bc2f9b8 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Mon, 8 Dec 2025 08:13:51 +0100
Subject: [PATCH v6 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 a0b1261f7d3..65173b9d9fd 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
[text/x-patch] v6-0004-Support-using-StaticAssert-macros-in-C.patch (2.5K, 5-v6-0004-Support-using-StaticAssert-macros-in-C.patch)
download | inline diff:
From 67f5f506dd13bac5e54790e864511c9eb3f5d88b Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Sat, 17 Jan 2026 14:51:36 +0100
Subject: [PATCH v6 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 65173b9d9fd..03764571dc8 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
[text/x-patch] v6-0005-Support-using-list_make-macros-in-C.patch (2.9K, 6-v6-0005-Support-using-list_make-macros-in-C.patch)
download | inline diff:
From fb4f07ba3ce998c5c2358e2434fea6825fe3edbe Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Sat, 17 Jan 2026 14:55:28 +0100
Subject: [PATCH v6 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
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Make copyObject work in C++
2026-01-03 09:32 Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-10 11:09 ` Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-14 15:59 ` Re: Make copyObject work in C++ Peter Eisentraut <[email protected]>
2026-01-17 15:25 ` Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
@ 2026-01-20 16:28 ` Peter Eisentraut <[email protected]>
2026-01-20 16:38 ` Re: Make copyObject work in C++ Andres Freund <[email protected]>
0 siblings, 1 reply; 7+ messages in thread
From: Peter Eisentraut @ 2026-01-20 16:28 UTC (permalink / raw)
To: Jelte Fennema-Nio <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; Thomas Munro <[email protected]>
On 17.01.26 16:25, Jelte Fennema-Nio wrote:
>> AFAICT, the minimum changes to get a minimum test module to work are
>>
>> - fix for "restrict", recently committed
>> - disable warning about zero-length arrays, seems trivial
>> - named designated initializers
>
> Correct, I've now restructured the commits to have the module
> introduction as the first one. Then all the other commits, both fix a
> macro to work in C++ and add some usage of those macros as coverage to
> the previously added module.
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.
I implemented a different solution for checking whether C++ is available
under configure. The runtime check from the makefile looked a bit
fragile. This way, we now have a "have_cxx" variable available in both
meson and makefiles.
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Make copyObject work in C++
2026-01-03 09:32 Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-10 11:09 ` Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-14 15:59 ` Re: Make copyObject work in C++ Peter Eisentraut <[email protected]>
2026-01-17 15:25 ` Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-20 16:28 ` Re: Make copyObject work in C++ Peter Eisentraut <[email protected]>
@ 2026-01-20 16:38 ` Andres Freund <[email protected]>
2026-01-20 19:07 ` Re: Make copyObject work in C++ Peter Eisentraut <[email protected]>
0 siblings, 1 reply; 7+ messages in thread
From: Andres Freund @ 2026-01-20 16:38 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; PostgreSQL Hackers <[email protected]>; Thomas Munro <[email protected]>
On 2026-01-20 17:28:00 +0100, Peter Eisentraut wrote:
> On 17.01.26 16:25, Jelte Fennema-Nio wrote:
> > > AFAICT, the minimum changes to get a minimum test module to work are
> > >
> > > - fix for "restrict", recently committed
> > > - disable warning about zero-length arrays, seems trivial
> > > - named designated initializers
> >
> > Correct, I've now restructured the commits to have the module
> > introduction as the first one. Then all the other commits, both fix a
> > macro to work in C++ and add some usage of those macros as coverage to
> > the previously added module.
>
> 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.
Seems like billbug doesn't like this:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=billbug&dt=2026-01-20%2016%3A00%3A02
gmake[1]: Entering directory '/home/marcel/build-farm-20/buildroot/HEAD/pgsql.build/src/test/modules/test_cplusplusext'
g++ -Wall -Wpointer-arith -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -g -O2 -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -I. -I. -I../../../../src/include -D_POSIX_C_SOURCE=200112L -D__EXTENSIONS__ -D_POSIX_PTHREAD_SEMANTICS -I/usr/openssl/3/include -I/usr/include/libxml2 -c -o test_cplusplusext.o test_cplusplusext.cpp
In file included from ../../../../src/include/postgres.h:48,
from test_cplusplusext.cpp:18:
../../../../src/include/c.h:158:21: error: '_Noreturn' does not name a type; did you mean 'pg_noreturn'?
158 | #define pg_noreturn _Noreturn
| ^~~~~~~~~
../../../../src/include/c.h:918:1: note: in expansion of macro 'pg_noreturn'
918 | pg_noreturn extern void ExceptionalCondition(const char *conditionName,
| ^~~~~~~~~~~
../../../../src/include/lib/stringinfo.h: In function 'void initStringInfoFromString(StringInfo, char*, int)':
../../../../src/include/c.h:890:25: error: 'ExceptionalCondition' was not declared in this scope
890 | ExceptionalCondition(#condition, __FILE__, __LINE__); \\
| ^~~~~~~~~~~~~~~~~~~~
../../../../src/include/lib/stringinfo.h:177:9: note: in expansion of macro 'Assert'
177 | Assert(data[len] == '\\0');
| ^~~~~~
../../../../src/include/utils/elog.h: At global scope:
../../../../src/include/c.h:158:21: error: '_Noreturn' does not name a type; did you mean 'pg_noreturn'?
158 | #define pg_noreturn _Noreturn
| ^~~~~~~~~
../../../../src/include/utils/elog.h:471:1: note: in expansion of macro 'pg_noreturn'
471 | pg_noreturn extern void ReThrowError(ErrorData *edata);
| ^~~~~~~~~~~
../../../../src/include/c.h:158:21: error: '_Noreturn' does not name a type; did you mean 'pg_noreturn'?
158 | #define pg_noreturn _Noreturn
| ^~~~~~~~~
../../../../src/include/utils/elog.h:473:1: note: in expansion of macro 'pg_noreturn'
473 | pg_noreturn extern void pg_re_throw(void);
| ^~~~~~~~~~~
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 7+ messages in thread
* Re: Make copyObject work in C++
2026-01-03 09:32 Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-10 11:09 ` Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-14 15:59 ` Re: Make copyObject work in C++ Peter Eisentraut <[email protected]>
2026-01-17 15:25 ` Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-20 16:28 ` Re: Make copyObject work in C++ Peter Eisentraut <[email protected]>
2026-01-20 16:38 ` Re: Make copyObject work in C++ Andres Freund <[email protected]>
@ 2026-01-20 19:07 ` Peter Eisentraut <[email protected]>
0 siblings, 0 replies; 7+ messages in thread
From: Peter Eisentraut @ 2026-01-20 19:07 UTC (permalink / raw)
To: Andres Freund <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; PostgreSQL Hackers <[email protected]>; Thomas Munro <[email protected]>
On 20.01.26 17:38, Andres Freund 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.
> Seems like billbug doesn't like this:
>
> https://buildfarm.postgresql.org/cgi-bin/show_log.pl?
> nm=billbug&dt=2026-01-20%2016%3A00%3A02
>
> gmake[1]: Entering directory '/home/marcel/build-farm-20/buildroot/HEAD/pgsql.build/src/test/modules/test_cplusplusext'
> g++ -Wall -Wpointer-arith -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -g -O2 -fPIC -fvisibility=hidden -fvisibility-inlines-hidden -I. -I. -I../../../../src/include -D_POSIX_C_SOURCE=200112L -D__EXTENSIONS__ -D_POSIX_PTHREAD_SEMANTICS -I/usr/openssl/3/include -I/usr/include/libxml2 -c -o test_cplusplusext.o test_cplusplusext.cpp
> In file included from ../../../../src/include/postgres.h:48,
> from test_cplusplusext.cpp:18:
> ../../../../src/include/c.h:158:21: error: '_Noreturn' does not name a type; did you mean 'pg_noreturn'?
> 158 | #define pg_noreturn _Noreturn
> | ^~~~~~~~~
> ../../../../src/include/c.h:918:1: note: in expansion of macro 'pg_noreturn'
> 918 | pg_noreturn extern void ExceptionalCondition(const char *conditionName,
> | ^~~~~~~~~~~
It's getting confused by _Noreturn, which is defined thus:
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
#define pg_noreturn _Noreturn
But apparently on these Solaris-related platforms, g++ defines
__STDC_VERSION__ even in C++ mode. (Confirmed in local testing.)
Apparently, this is even allowed by the C++ standard.
So the smallest fix is probably to gate this more like this:
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) &&
!defined(__cplusplus)
#define pg_noreturn _Noreturn
(Eventually, we could add support for C++ attributes, but one step at a
time.)
^ permalink raw reply [nested|flat] 7+ messages in thread
end of thread, other threads:[~2026-01-20 19:07 UTC | newest]
Thread overview: 7+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-01-03 09:32 Re: Make copyObject work in C++ Jelte Fennema-Nio <[email protected]>
2026-01-10 11:09 ` Jelte Fennema-Nio <[email protected]>
2026-01-14 15:59 ` Peter Eisentraut <[email protected]>
2026-01-17 15:25 ` Jelte Fennema-Nio <[email protected]>
2026-01-20 16:28 ` Peter Eisentraut <[email protected]>
2026-01-20 16:38 ` Andres Freund <[email protected]>
2026-01-20 19:07 ` Peter Eisentraut <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox