[PATCH] libstdc++: check value in std::{con,dis}junction

Jonathan Wakely jwakely.gcc@gmail.com
Wed Apr 29 22:12:32 GMT 2026


On Wed, 29 Apr 2026 at 22:52, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
>
> On Wed, 29 Apr 2026 at 22:38, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
> >
> > On Wed, 29 Apr 2026 at 22:33, Jonathan Wakely <jwakely.gcc@gmail.com> wrote:
> > >
> > > On Wed, 29 Apr 2026, 20:53 Alexandre Oliva, <oliva@adacore.com> wrote:
> > > >
> > > > Oops, I forgot to post the following to the libstdc++ mailing list.
> > > > https://gcc.gnu.org/pipermail/gcc-patches/2026-April/712661.html
> > > >
> > > > -------------------- Start of forwarded message --------------------
> > > > From: Alexandre Oliva <oliva@adacore.com>
> > > > To: gcc-patches@gcc.gnu.org
> > > > Subject: [PATCH] libstdc++: check value in std::{con,dis}junction
> > > > Date: Fri, 10 Apr 2026 09:02:49 -0300
> > > >
> > > >
> > > > When std::{con,dis}junction is passed a type that doesn't have a
> > > > "value" static data member, or whose value doesn't convert to bool, is
> > > > ambiguous or inaccessible, we stop iterating over the types and use
> > > > that type for the {con,dis}junction, without any diagnostic.
> > > >
> > > > We'd get a diagnostic when attempting to use its value data member as
> > > > a bool, but if the member isn't used, compilation is successful,
> > > > despite the failure to meet the requirements for "value" in
> > > > [meta.logical]/4.
> > > >
> > > > Check that the selected type parameter's value member is convertible
> > > > to bool.
> > > >
> > > > Regstrapped on x86_64-linux-gnu.  Ok to install?  Not a regression, so
> > > > for stage1 maybe?
> > >
> > >
> > > Hmm, this might be a regression caused by r13-2230-g390f94eee1ae69
> > >
> > > Before that change I think we required all template arguments to have
> > > a ::value that is implicitly convertible to bool. Now we only require
> > > explicit conversion, and we stop if we reach a type that fails to meet
> > > that condition.
> > >
> > > >
> > > >
> > > >
> > > > for  libstdc++-v3/ChangeLog
> > > >
> > > >         * include/std/type_traits (disjunction, conjunction): Check
> > > >         that value converts to bool.
> > > > ---
> > > >  libstdc++-v3/include/std/type_traits               |   15 ++++++++++-
> > > >  .../logical_traits/requirements/junction_neg.cc    |   27 ++++++++++++++++++++
> > > >  2 files changed, 40 insertions(+), 2 deletions(-)
> > > >  create mode 100644 libstdc++-v3/testsuite/20_util/logical_traits/requirements/junction_neg.cc
> > > >
> > > > diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
> > > > index 59be925e10a6c..238450518c083 100644
> > > > --- a/libstdc++-v3/include/std/type_traits
> > > > +++ b/libstdc++-v3/include/std/type_traits
> > > > @@ -241,10 +241,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > > >    } // namespace __detail
> > > >    /// @endcond
> > > >
> > > > +  template<typename _From, typename _To>
> > > > +    struct is_convertible;
> > > > +
> > > >    template<typename... _Bn>
> > > >      struct conjunction
> > > >      : __detail::__conjunction_impl<void, _Bn...>::type
> > > > -    { };
> > > > +    {
> > > > +      static_assert(is_convertible<decltype(__detail::__conjunction_impl<void, _Bn...>
> > > > +                                           ::type::value), bool>::value,
> > > > +                   "result type's value is not convertible to bool");
> > >
> > >
> > > The string literal should be phrased as "must be..." instead of "is not..."
> > >
> > > That avoids and possible confusion when the assertion fails and we the
> > > "assertion failed: X is not Y" string. Was the required condition "X
> > > is not Y" and that failed? Or is the condition "X is Y" and that is
> > > what failed?
> > >
> > > Saying "X must be Y" is clear what the required condition is.
> > >
> > > But I'm concerned that we enforce the static assert in all cases now,
> > > when we really only need to enforce it sometimes. An alternative
> > > implementation would be something like:
> > >
> > > --- a/libstdc++-v3/include/std/type_traits
> > > +++ b/libstdc++-v3/include/std/type_traits
> > > @@ -233,7 +233,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > >
> > >     template<typename /* = void */, typename _B1, typename... _Bn>
> > >       struct __conjunction_impl
> > > -      { using type = _B1; };
> > > +      {
> > > +       using type = _B1;
> > > +
> > > +       static_assert(is_convertible<decltype(type::value), bool>::value,
> > > +                     "type::value must be convertible to bool");
> > > +      };
> > >
> > >     template<typename _B1, typename _B2, typename... _Bn>
> > >       struct __conjunction_impl<__enable_if_t<bool(_B1::value)>, _B1,
> > > _B2, _Bn...>
> > >
> > > This only checks the condition in the case where bool(_B1::value) is
> > > either false or fails substitution (because it doesn't exist, or isn't
> > > accessible, or isn't explicitly convertible to bool).
>
> My suggestion to check in __conjunction_impl doesn't work for the
> ctTtf case, it fails to give an error. I don't immediately see why
> that is.

Ah, it _does_ work, but the static_assert for ctTtf comes from the
same instantiation as for cT and so is suppressed. I think G++ only
prints each unique static_assert failure once in a given scope.

We can solve that by using a different type, T2, in the ctTtf and dtTtf cases.

So here's a complete patch that passes the new test ...
-------------- next part --------------
commit 5d35fecac3e031ba9dc27942744bc8d82d1ef885
Author:     Alexandre Oliva <oliva@adacore.com>
AuthorDate: Wed Apr 29 23:02:53 2026
Commit:     Jonathan Wakely <redi@gcc.gnu.org>
CommitDate: Wed Apr 29 23:11:23 2026

    libstdc++: check value in std::{con,dis}junction
    
    When std::{con,dis}junction is passed a type that doesn't have a
    "value" static data member, or whose value doesn't convert to bool, is
    ambiguous or inaccessible, we stop iterating over the types and use
    that type for the {con,dis}junction, without any diagnostic.
    
    We'd get a diagnostic when attempting to use its value data member as
    a bool, but if the member isn't used, compilation is successful,
    despite the failure to meet the requirements for "value" in
    [meta.logical]/4.
    
    Check that the selected type parameter's value member is convertible
    to bool.
    
    libstdc++-v3/ChangeLog:
    
            * include/std/type_traits (disjunction, conjunction): Check
            that value converts to bool.
            * testsuite/20_util/logical_traits/requirements/junction_neg.cc:
            New test.
    
    Co-authored-by: Jonathan Wakely <jwakely@redhat.com>

diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
index 1c03c22e8eed..e6422ab5065a 100644
--- a/libstdc++-v3/include/std/type_traits
+++ b/libstdc++-v3/include/std/type_traits
@@ -213,74 +213,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     { };
   /// @endcond
 
-#ifdef __cpp_lib_logical_traits // C++ >= 17
-
-  /// @cond undocumented
-  template<typename... _Bn>
-    inline constexpr bool __or_v = __or_<_Bn...>::value;
-  template<typename... _Bn>
-    inline constexpr bool __and_v = __and_<_Bn...>::value;
-
-  namespace __detail
-  {
-    template<typename /* = void */, typename _B1, typename... _Bn>
-      struct __disjunction_impl
-      { using type = _B1; };
-
-    template<typename _B1, typename _B2, typename... _Bn>
-      struct __disjunction_impl<__enable_if_t<!bool(_B1::value)>, _B1, _B2, _Bn...>
-      { using type = typename __disjunction_impl<void, _B2, _Bn...>::type; };
-
-    template<typename /* = void */, typename _B1, typename... _Bn>
-      struct __conjunction_impl
-      { using type = _B1; };
-
-    template<typename _B1, typename _B2, typename... _Bn>
-      struct __conjunction_impl<__enable_if_t<bool(_B1::value)>, _B1, _B2, _Bn...>
-      { using type = typename __conjunction_impl<void, _B2, _Bn...>::type; };
-  } // namespace __detail
-  /// @endcond
-
-  template<typename... _Bn>
-    struct conjunction
-    : __detail::__conjunction_impl<void, _Bn...>::type
-    { };
-
-  template<>
-    struct conjunction<>
-    : true_type
-    { };
-
-  template<typename... _Bn>
-    struct disjunction
-    : __detail::__disjunction_impl<void, _Bn...>::type
-    { };
-
-  template<>
-    struct disjunction<>
-    : false_type
-    { };
-
-  template<typename _Pp>
-    struct negation
-    : __not_<_Pp>::type
-    { };
-
-  /** @ingroup variable_templates
-   * @{
-   */
-  template<typename... _Bn>
-    inline constexpr bool conjunction_v = conjunction<_Bn...>::value;
-
-  template<typename... _Bn>
-    inline constexpr bool disjunction_v = disjunction<_Bn...>::value;
-
-  template<typename _Pp>
-    inline constexpr bool negation_v = negation<_Pp>::value;
-  /// @}
-
-#endif // __cpp_lib_logical_traits
-
   // Forward declarations
   template<typename>
     struct is_object;
@@ -3871,6 +3803,81 @@ template<typename _Ret, typename _Fn, typename... _Args>
 /// @}
 #endif // __cpp_lib_type_trait_variable_templates
 
+#ifdef __cpp_lib_logical_traits // C++ >= 17
+
+  /// @cond undocumented
+  template<typename... _Bn>
+    inline constexpr bool __or_v = __or_<_Bn...>::value;
+  template<typename... _Bn>
+    inline constexpr bool __and_v = __and_<_Bn...>::value;
+
+  namespace __detail
+  {
+    template<typename /* = void */, typename _B1, typename... _Bn>
+      struct __disjunction_impl
+      {
+	using type = _B1;
+	static_assert(is_convertible_v<decltype(type::value), bool>);
+      };
+
+    template<typename _B1, typename _B2, typename... _Bn>
+      struct __disjunction_impl<__enable_if_t<!bool(_B1::value)>, _B1, _B2, _Bn...>
+      { using type = typename __disjunction_impl<void, _B2, _Bn...>::type; };
+
+    template<typename /* = void */, typename _B1, typename... _Bn>
+      struct __conjunction_impl
+      {
+	using type = _B1;
+	static_assert(is_convertible_v<decltype(type::value), bool>);
+      };
+
+    template<typename _B1, typename _B2, typename... _Bn>
+      struct __conjunction_impl<__enable_if_t<bool(_B1::value)>, _B1, _B2, _Bn...>
+      { using type = typename __conjunction_impl<void, _B2, _Bn...>::type; };
+  } // namespace __detail
+  /// @endcond
+
+  template<typename... _Bn>
+    struct conjunction
+    : __detail::__conjunction_impl<void, _Bn...>::type
+    { };
+
+  template<>
+    struct conjunction<>
+    : true_type
+    { };
+
+  template<typename... _Bn>
+    struct disjunction
+    : __detail::__disjunction_impl<void, _Bn...>::type
+    { };
+
+  template<>
+    struct disjunction<>
+    : false_type
+    { };
+
+  template<typename _Pp>
+    struct negation
+    : __not_<_Pp>::type
+    { };
+
+  /** @ingroup variable_templates
+   * @{
+   */
+  template<typename... _Bn>
+    inline constexpr bool conjunction_v = conjunction<_Bn...>::value;
+
+  template<typename... _Bn>
+    inline constexpr bool disjunction_v = disjunction<_Bn...>::value;
+
+  template<typename _Pp>
+    inline constexpr bool negation_v = negation<_Pp>::value;
+  /// @}
+
+#endif // __cpp_lib_logical_traits
+
+
 #ifdef __cpp_lib_has_unique_object_representations // C++ >= 17 && HAS_UNIQ_OBJ_REP
   /// has_unique_object_representations
   /// @since C++17
diff --git a/libstdc++-v3/testsuite/20_util/logical_traits/requirements/junction_neg.cc b/libstdc++-v3/testsuite/20_util/logical_traits/requirements/junction_neg.cc
new file mode 100644
index 000000000000..bb23bc482aed
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/logical_traits/requirements/junction_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do compile { target c++17 } }
+
+#include <type_traits>
+
+class S {}; // not convertible to bool
+
+struct T {
+  static constexpr S value{};
+};
+
+struct T2 {
+  static constexpr S value{};
+};
+
+// 4 Every template type argument for which Bi::value is instantiated shall be
+// usable as a base class and shall have a member value which is convertible to
+// bool, is not hidden, and is unambiguously available in the type.
+
+std::conjunction<T> cT; // { dg-error "here" }
+std::conjunction<T, std::true_type> cTt; // { dg-error "here" }
+std::conjunction<T, std::false_type> cTf; // { dg-error "here" }
+std::conjunction<std::true_type, T2> ctTtf; // { dg-error "here" }
+std::conjunction<std::false_type, T> cfTtf;
+
+std::disjunction<T> dT; // { dg-error "here" }
+std::disjunction<T, std::true_type> dTt; // { dg-error "here" }
+std::disjunction<T, std::false_type> dTf; // { dg-error "here" }
+std::disjunction<std::false_type, T2> dfTtf; // { dg-error "here" }
+std::disjunction<std::true_type, T> dtTtf;
+
+// { dg-prune-output "(static assertion failed|could not convert)" }


More information about the Libstdc++ mailing list