[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