[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