[PATCH v2] assert: Support assert as variadic macro for C++26 [PR27276]
Tomasz Kaminski
tkaminsk@redhat.com
Thu Feb 5 16:50:17 GMT 2026
On Thu, Feb 5, 2026 at 5:42 PM Tomasz Kamiński <tkaminsk@redhat.com> wrote:
> 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] { ... }
>
> The new expansion in form:
> (__VA_ARGS__) ? (void)(1 ? 1 : bool(__VA_ARGS__))
> : __assert_fail (...)
> Have 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)
>
> 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>
> ---
> v2:
> - expands commit description with explanation of why this
> particular expansion was selected
> - add additional tests, for lambdas, side-effects, and
> commented out example of multiple arguments
>
> assert/Makefile | 4 ++
> assert/assert.h | 36 +++++++----
> assert/test-assert-c++-variadic.cc | 99 ++++++++++++++++++++++++++++++
> assert/tst-assert-c++.cc | 38 +++++++++++-
> 4 files changed, 164 insertions(+), 13 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..ab2b425ba2
> --- /dev/null
> +++ b/assert/test-assert-c++-variadic.cc
> @@ -0,0 +1,99 @@
> +/* 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;
> +};
> +
> +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 ()));
> + }
> +}
> +
> +#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..8228c36781 100644
> --- a/assert/tst-assert-c++.cc
> +++ b/assert/tst-assert-c++.cc
> @@ -1,4 +1,4 @@
> -/* Tests for interactions between C++ and assert.
> +/* Tests for interactionsz between C++ and assert.
>
Fixed the addition of z and two stray spaces and end of the line in tests,
locally.
> Copyright (C) 2017-2026 Free Software Foundation, Inc.
> This file is part of the GNU C Library.
>
> @@ -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>
>
> --
> 2.53.0
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20260205/7240129f/attachment-0001.htm>
More information about the Libc-alpha
mailing list