[PATCH] assert: Support assert as variadic macro for C++26 [PR27276]

Tomasz Kamiński tkaminsk@redhat.com
Thu Feb 5 14:57:14 GMT 2026


From: Jonathan Wakely <jwakely@redhat.com>

C++26 changes assert into variadic macro to handle cases of using
assignment-expressions that would be interpreted as multiple macro
arguments, in particular one containing:
* template parameter lists: func<int, float>()
* call to overloaded operator[] that accepts multiple arguments: arr[1, 2]
  this is C++23 feature, see libstdc++ PR/119855 [1]
* lambda with explicit captures: [x, y] { ... }

As the new implementation relies on contextual conversion to bool
performed for the expression in condition of ternary operator,
enumerators of scoped enumeration are no longer accepted as assert
argument directly. Thus this patch address the glibc PR/27276 [2].

Mainly for testing purposes we allow enabling/disabling variadic assert
regardless of version of C or C++, by defining __ASSERT_VARIADIC to
1/0 respectively before inclusion of <assert>

[1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119855
[2] https://sourceware.org/bugzilla/show_bug.cgi?id=27276

Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
---
>From Tomasz:
Would appreciate feedback and suggestions on using __ASSERT_VARIADIC,
for the purposes of C++ testing. It is very similar to __ISOC23_SOURCE
macro used for C23 tests.

I have tested the patch using make check locally, and separately tested
cases that are expected to be invalid. For the scoped enumerations that
are now correctly ill-formed (glibc PR/27276) I have left commented
example in the test.

 assert/Makefile                    |  4 ++
 assert/assert.h                    | 36 ++++++++-----
 assert/test-assert-c++-variadic.cc | 87 ++++++++++++++++++++++++++++++
 assert/tst-assert-c++.cc           | 19 +++++++
 4 files changed, 134 insertions(+), 12 deletions(-)
 create mode 100644 assert/test-assert-c++-variadic.cc

diff --git a/assert/Makefile b/assert/Makefile
index f6f5eec1af..5437e97bfc 100644
--- a/assert/Makefile
+++ b/assert/Makefile
@@ -36,6 +36,7 @@ routines := \
 tests := \
   test-assert \
   test-assert-2 \
+  test-assert-c++-variadic \
   test-assert-c99 \
   test-assert-gnu99 \
   test-assert-perr \
@@ -49,12 +50,15 @@ CFLAGS-test-assert-c99.c += -std=c99
 CFLAGS-test-assert-gnu99.c += -std=gnu99
 
 ifeq ($(have-cxx-thread_local),yes)
+CFLAGS-test-assert-c++-variadic.o = -std=c++11
+LDLIBS-test-assert-c++-variadic = -lstdc++
 CFLAGS-tst-assert-c++.o = -std=c++11
 LDLIBS-tst-assert-c++ = -lstdc++
 CFLAGS-tst-assert-g++.o = -std=gnu++11
 LDLIBS-tst-assert-g++ = -lstdc++
 else
 tests-unsupported += \
+  test-assert-c++-variadic \
   tst-assert-c++ \
   tst-assert-g++ \
   # tests-unsupported
diff --git a/assert/assert.h b/assert/assert.h
index 53c9aa9c6b..746e5737eb 100644
--- a/assert/assert.h
+++ b/assert/assert.h
@@ -52,16 +52,20 @@
    comma in the initializer list, can be passed to assert.  This
    depends on support for variadic macros (added in C99 and GCC 2.95),
    and on support for _Bool (added in C99 and GCC 3.0) in order to
-   validate that only a single expression is passed as an argument,
-   and is currently implemented only for C.  */
-#if (__GLIBC_USE (ISOC23)						\
-     && (defined __GNUC__						\
-	 ? __GNUC_PREREQ (3, 0)						\
-	 : defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)	\
-     && !defined __cplusplus)
-# define __ASSERT_VARIADIC 1
-#else
-# define __ASSERT_VARIADIC 0
+   validate that only a single expression is passed as an argument.
+   Similary C++26 makes assert a variadic macro and allows expressions
+   containing template argument lists, lambda captures, and many others
+   that include commas, without requiring extra parentheses. */
+#if !defined __ASSERT_VARIADIC
+#  if (__GLIBC_USE (ISOC23)						\
+       && (defined __GNUC__						\
+	   ? __GNUC_PREREQ (3, 0)					\
+	   : defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)	\
+       || (defined __cplusplus && __cplusplus >= 202302L))
+#   define __ASSERT_VARIADIC 1
+#  else
+#    define __ASSERT_VARIADIC 0
+#  endif
 #endif
 
 /* void assert (int expression);
@@ -108,7 +112,7 @@ extern void __assert (const char *__assertion, const char *__file, int __line)
      __THROW __attribute__ ((__noreturn__)) __COLD;
 
 
-# if __ASSERT_VARIADIC
+# if __ASSERT_VARIADIC && !defined __cplusplus
 /* This function is not defined and is not called outside of an
    unevaluated sizeof, but serves to verify that the argument to
    assert is a single expression.  */
@@ -131,11 +135,19 @@ __END_DECLS
 #   define __ASSERT_FILE __FILE__
 #   define __ASSERT_LINE __LINE__
 #  endif
-#  define assert(expr)							\
+#  if __ASSERT_VARIADIC
+#    define assert(...)							\
+     ((__VA_ARGS__)							\
+      ? (void)(1 ? 1 : bool(__VA_ARGS__))				\
+      : __assert_fail (#__VA_ARGS__, __ASSERT_FILE, __ASSERT_LINE,	\
+                       __ASSERT_FUNCTION))
+#  else
+#    define assert(expr)						\
      (static_cast <bool> (expr)						\
       ? void (0)							\
       : __assert_fail (#expr, __ASSERT_FILE, __ASSERT_LINE,             \
                        __ASSERT_FUNCTION))
+#  endif
 # elif !defined __GNUC__ || defined __STRICT_ANSI__
 #  if __ASSERT_VARIADIC
 #   define assert(...)							\
diff --git a/assert/test-assert-c++-variadic.cc b/assert/test-assert-c++-variadic.cc
new file mode 100644
index 0000000000..dde8567854
--- /dev/null
+++ b/assert/test-assert-c++-variadic.cc
@@ -0,0 +1,87 @@
+/* Test assert as a variadic macro for C++ code snippets.
+   Copyright The GNU Toolchain Authors.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* Some versions of GCC supported for building glibc do not support
+   -std=c++26 and -std=c++2c (added in GCC 14), so we compile
+   with -std=c++11 and force assert to be variadic.  */
+#define _ASSERT_VARIADIC 1
+#undef NDEBUG
+#include <assert.h>
+
+#if __GNUC_PREREQ (5, 0)
+template <typename T1, typename T2>
+bool
+foo ()
+{ return true; }
+
+struct C
+{
+  C (int p, int r) : x (p + r) {}
+
+  int x;
+};
+
+static void
+test_enabled ()
+{
+  {
+    assert (foo<int, float> ());
+  }
+
+  {
+    assert (C {1, 2}.x > 0);
+  }
+
+  {
+    int x = 10, y = 20;
+    assert ([x, y] { return x < y; } ());
+  }
+}
+
+#define NDEBUG
+#include <assert.h>
+
+static void
+test_disabled ()
+{
+  /* Assert is variadic, but ignores arguments */
+  assert(1, 2);
+  assert(+, 1, -, 2, *, 30);
+}
+
+static int
+do_test ()
+{
+  test_enabled ();
+  test_disabled ();
+  return 0;
+}
+
+#else
+#include <support/test-driver.h>
+
+static int
+do_test ()
+{
+  return EXIT_UNSUPPORTED;
+}
+
+#endif
+
+#include <support/test-driver.c>
+
diff --git a/assert/tst-assert-c++.cc b/assert/tst-assert-c++.cc
index 1e7d971bb0..7fb8bbfe26 100644
--- a/assert/tst-assert-c++.cc
+++ b/assert/tst-assert-c++.cc
@@ -22,6 +22,11 @@
 #include <assert.h>
 
 #if __GNUC_PREREQ (5, 0)
+template<typename> struct is_void { static const bool value = false; };
+template<> struct is_void<void> { static const bool value = true; };
+
+static_assert(is_void<decltype(assert(""))>::value, "type is void");
+
 /* The C++ standard requires that if the assert argument is a constant
    subexpression, then the assert itself is one, too.  */
 constexpr int
@@ -63,6 +68,9 @@ struct bool_and_int
   template <class T> bool operator!= (T) const; /* No definition.  */
 };
 
+/* Scoped enumerations are not contextually convertible to bool. */
+enum class E { e1 = 1 };
+
 static int
 do_test ()
 {
@@ -76,8 +84,19 @@ do_test ()
     assert (value);
   }
 
+  {
+    assert(bool(E::e1));
+    /* Ill-formed, E::e1 is not contextually convertible to bool. */
+    // assert(E::e1);
+  }
+
   return 0;
 }
+#define NDEBUG
+#include <assert.h>
+
+static_assert(is_void<decltype(assert(""))>::value, "type is void with NDEBUG");
+
 #else
 #include <support/test-driver.h>
 
-- 
2.53.0



More information about the Libc-alpha mailing list