On Fri Feb 20, 2026 at 10:47 AM CET, Jelte Fennema-Nio wrote:
Makes total sense, I didn't realise decltype and typeof were not quite the same thing. Attached is an updated patchset that does that.
Same patchset as before, but now also including a C++ fallback for __builtin_types_compatible_p.
From 077bb1a951c4eb6d61ba2e977c6b09d8d295831a Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio <[email protected]> Date: Fri, 5 Dec 2025 15:37:59 +0100 Subject: [PATCH v10 1/4] Support using copyObject in C++ Calling copyObject in C++ without GNU extensions (e.g. then using -std=c++11 instead of -std=gnu++11) fails with an error like this: error: use of undeclared identifier 'typeof'; did you mean 'typeid' This is due to the C compiler used to compile postgres supporting typeof, but that function actually not being present in the C++ compiler. This fixes that by explicitely checking for typeof support in C++ and then either use that, or define typeof ourselves as: std::remove_reference_t<decltype(x)> According to the paper that led to adding typeof to the C standard, that's the C++ equivalent of the C typeof: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2927.htm#existing-decltype --- config/c-compiler.m4 | 29 ++++++++++ configure | 54 +++++++++++++++++++ configure.ac | 1 + meson.build | 25 +++++++++ src/include/c.h | 21 ++++++++ src/include/pg_config.h.in | 4 ++ .../test_cplusplusext/test_cplusplusext.cpp | 2 + 7 files changed, 136 insertions(+) diff --git a/config/c-compiler.m4 b/config/c-compiler.m4 index 1509dbfa2ab..5b3cbc7e8e8 100644 --- a/config/c-compiler.m4 +++ b/config/c-compiler.m4 @@ -193,6 +193,35 @@ fi])# PGAC_C_TYPEOF +# PGAC_CXX_TYPEOF +# ---------------- +# Check if the C++ compiler understands typeof or a variant. Define +# HAVE_CXX_TYPEOF if so, and define 'pg_cxx_typeof' to the actual key word. +# +AC_DEFUN([PGAC_CXX_TYPEOF], +[AC_CACHE_CHECK(for C++ typeof, pgac_cv_cxx_typeof, +[pgac_cv_cxx_typeof=no +AC_LANG_PUSH(C++) +for pgac_kw in typeof __typeof__; do + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], +[int x = 0; +$pgac_kw(x) y; +y = x; +return y;])], +[pgac_cv_cxx_typeof=$pgac_kw]) + test "$pgac_cv_cxx_typeof" != no && break +done +AC_LANG_POP([])]) +if test "$pgac_cv_cxx_typeof" != no; then + AC_DEFINE(HAVE_CXX_TYPEOF, 1, + [Define to 1 if your C++ compiler understands `typeof' or something similar.]) + if test "$pgac_cv_cxx_typeof" != typeof; then + AC_DEFINE_UNQUOTED(pg_cxx_typeof, $pgac_cv_cxx_typeof, [Define to how the C++ compiler spells `typeof'.]) + fi +fi])# PGAC_CXX_TYPEOF + + + # PGAC_C_TYPES_COMPATIBLE # ----------------------- # Check if the C compiler understands __builtin_types_compatible_p, diff --git a/configure b/configure index a285a6ec3d7..604f9e0100b 100755 --- a/configure +++ b/configure @@ -15078,6 +15078,60 @@ _ACEOF fi fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ typeof" >&5 +$as_echo_n "checking for C++ typeof... " >&6; } +if ${pgac_cv_cxx_typeof+:} false; then : + $as_echo_n "(cached) " >&6 +else + pgac_cv_cxx_typeof=no +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +for pgac_kw in typeof __typeof__; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +int x = 0; +$pgac_kw(x) y; +y = x; +return y; + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO"; then : + pgac_cv_cxx_typeof=$pgac_kw +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + test "$pgac_cv_cxx_typeof" != no && break +done +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_cxx_typeof" >&5 +$as_echo "$pgac_cv_cxx_typeof" >&6; } +if test "$pgac_cv_cxx_typeof" != no; then + +$as_echo "#define HAVE_CXX_TYPEOF 1" >>confdefs.h + + if test "$pgac_cv_cxx_typeof" != typeof; then + +cat >>confdefs.h <<_ACEOF +#define pg_cxx_typeof $pgac_cv_cxx_typeof +_ACEOF + + fi +fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __builtin_types_compatible_p" >&5 $as_echo_n "checking for __builtin_types_compatible_p... " >&6; } if ${pgac_cv__types_compatible+:} false; then : diff --git a/configure.ac b/configure.ac index 476a76c7991..93ecf41447b 100644 --- a/configure.ac +++ b/configure.ac @@ -1732,6 +1732,7 @@ PGAC_PRINTF_ARCHETYPE PGAC_CXX_PRINTF_ARCHETYPE PGAC_C_STATEMENT_EXPRESSIONS PGAC_C_TYPEOF +PGAC_CXX_TYPEOF PGAC_C_TYPES_COMPATIBLE PGAC_C_BUILTIN_CONSTANT_P PGAC_C_BUILTIN_OP_OVERFLOW diff --git a/meson.build b/meson.build index 5122706477d..9cc95f0be5f 100644 --- a/meson.build +++ b/meson.build @@ -2953,6 +2953,31 @@ int main(void) endif endforeach +# Check if the C++ compiler understands typeof or a variant. +if have_cxx + foreach kw : ['typeof', '__typeof__'] + if cxx.compiles(''' +int main(void) +{ + int x = 0; + @0@(x) y; + y = x; + return y; +} +'''.format(kw), + name: 'C++ ' + kw, + args: test_c_args, include_directories: postgres_inc) + + cdata.set('HAVE_CXX_TYPEOF', 1) + if kw != 'typeof' + cdata.set('pg_cxx_typeof', kw) + endif + + break + endif + endforeach +endif + # MSVC doesn't cope well with defining restrict to __restrict, the # spelling it understands, because it conflicts with diff --git a/src/include/c.h b/src/include/c.h index fb0ea1bc680..a4fee84398d 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -424,6 +424,27 @@ #define unlikely(x) ((x) != 0) #endif +/* + * Provide typeof in C++ for C++ compilers that don't support typeof natively. + * It might be spelled __typeof__ instead of typeof, in which case + * pg_cxx_typeof provides that mapping. If neither is supported, we can use + * decltype, but to make it equivalent to C's typeof, we need to remove + * references from the result [1]. Also ensure HAVE_TYPEOF is set so that + * typeof-dependent code is always enabled in C++ mode. + * + * [1]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2927.htm#existing-decltype + */ +#if defined(__cplusplus) +#ifdef pg_cxx_typeof +#define typeof(x) pg_cxx_typeof(x) +#elif !defined(HAVE_CXX_TYPEOF) +#define typeof(x) std::remove_reference_t<decltype(x)> +#endif +#ifndef HAVE_TYPEOF +#define HAVE_TYPEOF 1 +#endif +#endif + /* * CppAsString * Convert the argument to a string, using the C preprocessor. diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index a0bd84376e7..a970577ae3e 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -451,6 +451,10 @@ /* Define to 1 if your compiler understands `typeof' or something similar. */ #undef HAVE_TYPEOF +/* Define to 1 if your C++ compiler understands `typeof' or something similar. + */ +#undef HAVE_CXX_TYPEOF + /* Define to 1 if you have the <uchar.h> header file. */ #undef HAVE_UCHAR_H diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp index eb129dd15d4..ea04a761184 100644 --- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp +++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp @@ -37,6 +37,7 @@ test_cplusplus_add(PG_FUNCTION_ARGS) int32 a = PG_GETARG_INT32(0); int32 b = PG_GETARG_INT32(1); RangeTblRef *node = makeNode(RangeTblRef); + RangeTblRef *copy = copyObject(node); List *list = list_make1(node); foreach_ptr(RangeTblRef, rtr, list) @@ -54,6 +55,7 @@ test_cplusplus_add(PG_FUNCTION_ARGS) list_free(list); pfree(node); + pfree(copy); switch (a) { base-commit: 284925508ae685a63ee056f89a336caecab64a63 -- 2.53.0
From c8fd6666836c7696c49708e1afb785cad3b55f13 Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio <[email protected]> Date: Mon, 8 Dec 2025 08:13:51 +0100 Subject: [PATCH v10 2/4] Use typeof everywhere instead of compiler specific spellings We define typeof ourselves as __typeof__ if it does not exist. So let's actually use that for consistency. The meson/autoconf checks for __builtin_types_compatible_p still use __typeof__ though, because there we have not redefined it. --- src/include/c.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/include/c.h b/src/include/c.h index a4fee84398d..df01acdf6ba 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -1008,10 +1008,10 @@ pg_noreturn extern void ExceptionalCondition(const char *conditionName, */ #ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P #define StaticAssertVariableIsOfType(varname, typename) \ - StaticAssertDecl(__builtin_types_compatible_p(__typeof__(varname), typename), \ + StaticAssertDecl(__builtin_types_compatible_p(typeof(varname), typename), \ CppAsString(varname) " does not have type " CppAsString(typename)) #define StaticAssertVariableIsOfTypeMacro(varname, typename) \ - (StaticAssertExpr(__builtin_types_compatible_p(__typeof__(varname), typename), \ + (StaticAssertExpr(__builtin_types_compatible_p(typeof(varname), typename), \ CppAsString(varname) " does not have type " CppAsString(typename))) #else /* !HAVE__BUILTIN_TYPES_COMPATIBLE_P */ #define StaticAssertVariableIsOfType(varname, typename) \ @@ -1275,11 +1275,11 @@ typedef struct PGAlignedXLogBlock PGAlignedXLogBlock; #define unvolatize(underlying_type, expr) const_cast<underlying_type>(expr) #elif defined(HAVE__BUILTIN_TYPES_COMPATIBLE_P) #define unconstify(underlying_type, expr) \ - (StaticAssertExpr(__builtin_types_compatible_p(__typeof(expr), const underlying_type), \ + (StaticAssertExpr(__builtin_types_compatible_p(typeof(expr), const underlying_type), \ "wrong cast"), \ (underlying_type) (expr)) #define unvolatize(underlying_type, expr) \ - (StaticAssertExpr(__builtin_types_compatible_p(__typeof(expr), volatile underlying_type), \ + (StaticAssertExpr(__builtin_types_compatible_p(typeof(expr), volatile underlying_type), \ "wrong cast"), \ (underlying_type) (expr)) #else -- 2.53.0
From 8abacdb5305619a19fb3dc7eaa53d4cc4d7d805e Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio <[email protected]> Date: Thu, 19 Feb 2026 23:55:43 +0100 Subject: [PATCH v10 3/4] Make unconstify and unvolatize use StaticAssertVariableIsOfTypeMacro The unconstify and unvolatize macros had an almost identical assertion as was already defined in StaticAssertVariableIsOfTypeMacro, only it had a less useful error message and didn't have a sizeof fallback. --- src/include/c.h | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/include/c.h b/src/include/c.h index df01acdf6ba..c92b214f6b0 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -1273,20 +1273,13 @@ typedef struct PGAlignedXLogBlock PGAlignedXLogBlock; #if defined(__cplusplus) #define unconstify(underlying_type, expr) const_cast<underlying_type>(expr) #define unvolatize(underlying_type, expr) const_cast<underlying_type>(expr) -#elif defined(HAVE__BUILTIN_TYPES_COMPATIBLE_P) +#else #define unconstify(underlying_type, expr) \ - (StaticAssertExpr(__builtin_types_compatible_p(typeof(expr), const underlying_type), \ - "wrong cast"), \ + (StaticAssertVariableIsOfTypeMacro(expr, const underlying_type), \ (underlying_type) (expr)) #define unvolatize(underlying_type, expr) \ - (StaticAssertExpr(__builtin_types_compatible_p(typeof(expr), volatile underlying_type), \ - "wrong cast"), \ + (StaticAssertVariableIsOfTypeMacro(expr, volatile underlying_type), \ (underlying_type) (expr)) -#else -#define unconstify(underlying_type, expr) \ - ((underlying_type) (expr)) -#define unvolatize(underlying_type, expr) \ - ((underlying_type) (expr)) #endif /* -- 2.53.0
From 437a8aece153c5dc24c0e2708c034fe1718ea3bf Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio <[email protected]> Date: Fri, 13 Feb 2026 09:39:34 +0100 Subject: [PATCH v10 4/4] Add C++ version of __builtin_types_compatible_p StaticAssertVariableIsOfType would fail to compile in C++ extensions if meson/autoconf has detected HAVE__BUILTIN_TYPES_COMPATIBLE_P to be true for the C compiler. Basically equivalent to the PG_PRINTF_ATTRIBUTE situation in 0909380e4c9. This fixes that problem by defining __builtin_types_compatible_p in C++ using an equivalent STL constructs. --- src/include/c.h | 22 +++++++++++++++++++ .../test_cplusplusext/test_cplusplusext.cpp | 20 ++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/include/c.h b/src/include/c.h index c92b214f6b0..ba629a3503b 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -80,6 +80,12 @@ #ifdef HAVE_XLOCALE_H #include <xlocale.h> #endif +#ifdef __cplusplus +extern "C++" +{ +#include <type_traits> +} +#endif #ifdef ENABLE_NLS #include <libintl.h> #endif @@ -443,7 +449,23 @@ #ifndef HAVE_TYPEOF #define HAVE_TYPEOF 1 #endif +/* + * Provide __builtin_types_compatible_p for C++ by comparing types with + * std::is_same after stripping cv-qualifiers. The second branch handles + * GCC's special case where an incomplete array (e.g. int[]) is considered + * compatible with a complete array of the same element type (e.g. int[3]). + */ +#define __builtin_types_compatible_p(x, y) \ + (std::is_same<std::remove_cv_t<x>, std::remove_cv_t<y>>::value || \ + (std::is_array<x>::value && std::is_array<y>::value && \ + (std::extent<x>::value == 0 || std::extent<y>::value == 0) && \ + std::is_same<std::remove_cv_t<std::remove_extent_t<x>>, \ + std::remove_cv_t<std::remove_extent_t<y>>>::value)) +#ifndef HAVE__BUILTIN_TYPES_COMPATIBLE_P +#define HAVE__BUILTIN_TYPES_COMPATIBLE_P 1 #endif +#endif + /* * CppAsString diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp index ea04a761184..936ca422c76 100644 --- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp +++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp @@ -34,11 +34,14 @@ StaticAssertDecl(sizeof(int32) == 4, "int32 should be 4 bytes"); extern "C" Datum test_cplusplus_add(PG_FUNCTION_ARGS) { - int32 a = PG_GETARG_INT32(0); + const int32 a = PG_GETARG_INT32(0); int32 b = PG_GETARG_INT32(1); + const char *p = ""; RangeTblRef *node = makeNode(RangeTblRef); RangeTblRef *copy = copyObject(node); List *list = list_make1(node); + const int32 int_array[3] = {0}; + extern int32 incomplete_array[]; foreach_ptr(RangeTblRef, rtr, list) { @@ -52,6 +55,21 @@ test_cplusplus_add(PG_FUNCTION_ARGS) StaticAssertStmt(sizeof(int32) == 4, "int32 should be 4 bytes"); (void) StaticAssertExpr(sizeof(int64) == 8, "int64 should be 8 bytes"); + StaticAssertVariableIsOfType(a, int32); + StaticAssertVariableIsOfType(a, const int32); + StaticAssertVariableIsOfType(b, const int32); + StaticAssertVariableIsOfType(int_array, int32[3]); + StaticAssertVariableIsOfType(int_array, const int32[3]); + StaticAssertVariableIsOfType(p, const char *); + StaticAssertVariableIsOfTypeMacro(a, int32); + StaticAssertVariableIsOfTypeMacro(a, const int32); + StaticAssertVariableIsOfTypeMacro(b, int32); + StaticAssertVariableIsOfTypeMacro(int_array, int32[3]); + StaticAssertVariableIsOfTypeMacro(int_array, const int32[3]); + StaticAssertVariableIsOfTypeMacro(p, const char *); + /* incomplete array matches complete array type */ + StaticAssertVariableIsOfType(incomplete_array, int32[3]); + StaticAssertVariableIsOfTypeMacro(incomplete_array, int32[3]); list_free(list); pfree(node); -- 2.53.0
