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

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Wed Feb 25 13:40:34 GMT 2026



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).

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);
>  



More information about the Libc-alpha mailing list