[PATCH v6] assert: Support assert as variadic macro for C++26 [PR27276]
Tomasz Kaminski
tkaminsk@redhat.com
Wed Feb 25 14:00:13 GMT 2026
On Wed, Feb 25, 2026 at 2:40 PM Adhemerval Zanella Netto <
adhemerval.zanella@linaro.org> wrote:
>
>
> On 19/02/26 11:54, Tomasz Kamiński wrote:
> > 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
>
> The new tests fails with clang-18:
>
> test-assert-c++-variadic.cc:72:22: error: predefined identifier is only
> valid inside function [-Werror,-Wpredefined-identifier-outside-function]
> 72 | constexpr decltype ((assert (val <Ts> ()), false))
> | ^
> ../assert/assert.h:141:24: note: expanded from macro 'assert'
> 141 | __ASSERT_FUNCTION))
> | ^
> ../assert/assert.h:199:44: note: expanded from macro '__ASSERT_FUNCTION'
> 199 | # define __ASSERT_FUNCTION __extension__ __PRETTY_FUNCTION__
> | ^
>
> And I got an ICE for clang-21 and clang-22. The assert/tst-assert-c++.cc
> also triggers issues, and we do not enable them for clang for unrelated
> reasons (commit 5e17b4c9837).
>
I would prefer the test code to be compiled with clang, and the above issue
is pretty easy to address, now that we limit the test for C++26. I will
post
Updated patch soon.
>
> So I think it would be better to also check for '#if __GNUC_PREREQ (14, 0)'
> on the new test.
>
>
> >
> > 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);
> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20260225/3648046d/attachment-0001.htm>
More information about the Libc-alpha
mailing list