[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