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

Tomasz Kamiński tkaminsk@redhat.com
Thu Feb 19 14:54:02 GMT 2026


From: Jonathan Wakely <jwakely@redhat.com>

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

The new expansion in form:
  (__VA_ARGS__) ? void (1 ? 1 : bool (__VA_ARGS__))
                : __assert_fail (...)
Has the following properties:
* Use of (__VA_ARGS__) ? ... : ..., requires that __VA_ARGS__
  is contextually convertible to bool. This means that enumerators
  of scoped enumeration are no longer accepted (they are only
  explicitly convertible). Thus this patch address the glibc PR/27276 [2].
* Nested ternary 1 ? 1 : bool (__VA_ARGS__) guarantees that
  expression expanded from __VA_ARGS__ is not evaluated twice.
  This is used instead of unevaluated context (like sizeof...)
  to support C++ expressions that are not allowed in unevaluated
  context (lambdas until C++20, co_await, co_yield).
* bool (__VA_ARGS__) is ill-formed if __VA_ARGS__ expands to
  multiple arguments: assert(1, 2)
* bool (__VA_ARGS__) also triggers warnings when __VA_ARGS__
  expands to x = 1: assert(x = 1)

To guarantee that the code snippets from assert/test-assert-c++-variadic.cc,
are actually checked for validity, we need to compile this test in C++26
(-std=c++26) mode. To achieve that, this patch compiles the file with
test-config-cxxflags-stdcxx26 variable as additional flag, that is set to
-std=c++26 if $(TEST_CXX) executable supports that flag, and empty otherwise.

[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>
---
v6:
- reverts changes to __ASSERT_VARIADIC, and introduces
  test-config-cxxflags-stdcxx26 configure variable instead,
  to run test in C++26 when supported
- adds explanatory comments before C++26 assert definition
- fixes formatting

 assert/Makefile                    |   4 +
 assert/assert.h                    |  28 ++++---
 assert/test-assert-c++-variadic.cc | 124 +++++++++++++++++++++++++++++
 assert/tst-assert-c++.cc           |  36 +++++++++
 configure                          |  34 ++++++++
 configure.ac                       |  10 +++
 6 files changed, 227 insertions(+), 9 deletions(-)
 create mode 100644 assert/test-assert-c++-variadic.cc

diff --git a/assert/Makefile b/assert/Makefile
index f6f5eec1af..4c253a344a 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 = $(test-config-cxxflags-stdcxx26)
+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..471e225ea6 100644
--- a/assert/assert.h
+++ b/assert/assert.h
@@ -52,13 +52,12 @@
    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)
+   validate that only a single expression is passed as an argument.  */
+#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
@@ -108,7 +107,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 +130,22 @@ __END_DECLS
 #   define __ASSERT_FILE __FILE__
 #   define __ASSERT_LINE __LINE__
 #  endif
-#  define assert(expr)							\
+#  if __ASSERT_VARIADIC
+/* The first test of __VA_ARGS__ evaluates it without converting scoped
+   enumeration values to bool, and the second test checks that it is a
+   single expression without evaluating it.  */
+#    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..bd87298bd8
--- /dev/null
+++ b/assert/test-assert-c++-variadic.cc
@@ -0,0 +1,124 @@
+/* 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/>.  */
+
+/* This test requires C++26, and is compiled with -std=c++26
+   if GCC version supports that, and no additional options
+   otherwise. */
+#if defined __cplusplus && __cplusplus > 202302L
+
+#undef NDEBUG
+#include <assert.h>
+
+template <typename T1, typename T2>
+bool
+foo ()
+{ return true; }
+
+struct C
+{
+  C (int p, int r) : x (p + r) {}
+
+  int x;
+};
+
+int
+func ()
+{
+  return 1;
+}
+
+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; } ());
+  }
+
+  {
+    /* Ill-formed, not an assigment expression. */
+    // assert (func (), func ());
+    assert ((func (), func ()));
+  }
+}
+
+template <typename T>
+T val();
+
+template <typename Ts>
+constexpr decltype ((assert (val <Ts> ()), false))
+assert_works (int)
+{
+  return true;
+}
+
+template <typename Ts>
+constexpr bool
+assert_works (...)
+{
+  return false;
+}
+
+enum OE { oe };
+enum TE : int { te };
+enum class SE : int { se };
+
+static_assert ( assert_works <OE> (0));
+static_assert ( assert_works <TE> (0));
+static_assert (!assert_works <SE> (0));
+
+#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..959eef3740 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,15 @@ 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 };
+
+int&
+preincrement (int& i)
+{
+  return ++i;
+}
+
 static int
 do_test ()
 {
@@ -76,8 +90,30 @@ do_test ()
     assert (value);
   }
 
+  {
+    assert ([] { return true; } ());
+  }
+
+  {
+    assert (bool (E::e1));
+    /* Ill-formed, E::e1 is not contextually convertible to bool. */
+    // assert (E::e1);
+  }
+
+  {
+    int i = 0;
+    assert (preincrement (i) > 0);
+    if (i != 1)
+      return 1;
+  }
+
   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>
 
diff --git a/configure b/configure
index 0cd2269859..0841355583 100755
--- a/configure
+++ b/configure
@@ -8344,6 +8344,40 @@ CXX="$saved_CXX"
 config_vars="$config_vars
 test-config-cxxflags-finput-charset-ascii = $libc_cv_test_cxxflags_finput_charset_ascii"
 
+
+
+saved_CXX="$CXX"
+CXX="$TEST_CXX"
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking $CXX -std=c++26 in testing" >&5
+printf %s "checking $CXX -std=c++26 in testing... " >&6; }
+if test ${libc_cv_test_cxxflags_stdcxx26+y}
+then :
+  printf %s "(cached) " >&6
+else case e in #(
+  e) if { ac_try='${CXX-c++} -Werror -std=c++26 -xc++ /dev/null -S -o /dev/null'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+then :
+  libc_cv_test_cxxflags_stdcxx26="-std=c++26"
+else case e in #(
+  e) libc_cv_test_cxxflags_stdcxx26=
+ ;;
+esac
+fi ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_test_cxxflags_stdcxx26" >&5
+printf "%s\n" "$libc_cv_test_cxxflags_stdcxx26" >&6; }
+
+CXX="$saved_CXX"
+
+
+config_vars="$config_vars
+test-config-cxxflags-stdcxx26 = $libc_cv_test_cxxflags_stdcxx26"
+
 conftest_code="
 extern int not_exist (void);
 
diff --git a/configure.ac b/configure.ac
index 3e4a601c01..ea81b0ea62 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1696,6 +1696,16 @@ LIBC_TRY_TEST_CXX_OPTION([$CXX -finput-charset=ascii],
 LIBC_CONFIG_VAR(test-config-cxxflags-finput-charset-ascii,
 		$libc_cv_test_cxxflags_finput_charset_ascii)
 
+dnl Check if TEST_CXX supports -std=c++26.
+LIBC_TRY_TEST_CXX_OPTION([$CXX -std=c++26],
+  [-Werror -std=c++26],
+  libc_cv_test_cxxflags_stdcxx26,
+  [libc_cv_test_cxxflags_stdcxx26="-std=c++26"],
+  [libc_cv_test_cxxflags_stdcxx26=]
+)
+LIBC_CONFIG_VAR(test-config-cxxflags-stdcxx26,
+		$libc_cv_test_cxxflags_stdcxx26)
+
 conftest_code="
 extern int not_exist (void);
 
-- 
2.53.0



More information about the Libc-alpha mailing list