[PATCH] libstdc++: check value in std::{con,dis}junction
Patrick Palka
ppalka@redhat.com
Wed Apr 29 23:07:18 GMT 2026
On Wed, 29 Apr 2026, Jonathan Wakely 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).
>
> Patrick, did we discuss the change from implicitly convertible to bool
> to explicitly convertible to bool? r13-2230-g390f94eee1ae69 changed
> the implementation from __conditional_t<_B1::value, ...> to
> __enable_if_t<bool(_B1::value)>.
I don't remember to what extent we discussed it, but the explicit bool
cast is needed for the optimized std::disjunction implementation which
does __enable_if_t<!bool(_B1::value)>. Without the explicit cast, we'd
potentially get undesirable ADL for operator! as you pointed out. I
added the explicit cast to std::conjunction for symmetry.
I thought the spec was clear that only explicit convertibility is
required for con/disjunction based p3 and p5, but now I'm not so sure...
If implicit convertibility is required then that'll somewhat pessimize
this optimized implementation.
Note that std::negation is clearly specified in terms of explicit
converibility, so I guess it's not a stretch to require the same
for conjunction/disjunction (despite the fuzzy wording), especially
if we want De Morgan's laws to hold for these traits.
Turns out I had a patch for this issue that I never posted, here it is.
I used (bool(_B1::value) || true) as the assertion to avoid having to
instantiate is_convertible, but of course that only requires explicit
convertibility.
Subject: [PATCH] libstdc++: Fix std::con/disjunction for invalid arguments
libstdc++-v3/ChangeLog:
* include/std/type_traits:
* testsuite/20_util/logical_traits/requirements/value_member_neg.cc: New test.
---
libstdc++-v3/include/std/type_traits | 20 ++++++++++++--
.../requirements/value_member_neg.cc | 26 +++++++++++++++++++
2 files changed, 44 insertions(+), 2 deletions(-)
create mode 100644 libstdc++-v3/testsuite/20_util/logical_traits/requirements/value_member_neg.cc
diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
index 1c03c22e8eed..e7c856794d6f 100644
--- a/libstdc++-v3/include/std/type_traits
+++ b/libstdc++-v3/include/std/type_traits
@@ -225,7 +225,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{
template<typename /* = void */, typename _B1, typename... _Bn>
struct __disjunction_impl
- { using type = _B1; };
+ {
+ using type = _B1;
+
+ // The SFINAE-based recursion could have short-circuited due to
+ // _B1::value being unusable rather than it evaluating to true.
+ // According to [meta.logical]/9, this renders the program ill-formed;
+ // the below assert enforces this.
+ static_assert(bool(_B1::value) || true); // verify value member is usable
+ };
template<typename _B1, typename _B2, typename... _Bn>
struct __disjunction_impl<__enable_if_t<!bool(_B1::value)>, _B1, _B2, _Bn...>
@@ -233,7 +241,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename /* = void */, typename _B1, typename... _Bn>
struct __conjunction_impl
- { using type = _B1; };
+ {
+ using type = _B1;
+
+ // The SFINAE-based recursion could have short-circuited due to
+ // _B1::value being unusable rather than it evaluating to false.
+ // According to [meta.logical]/4, this renders the program ill-formed;
+ // the below assert enforces this.
+ static_assert(bool(_B1::value) || true); // verify value member is usable
+ };
template<typename _B1, typename _B2, typename... _Bn>
struct __conjunction_impl<__enable_if_t<bool(_B1::value)>, _B1, _B2, _Bn...>
diff --git a/libstdc++-v3/testsuite/20_util/logical_traits/requirements/value_member_neg.cc b/libstdc++-v3/testsuite/20_util/logical_traits/requirements/value_member_neg.cc
new file mode 100644
index 000000000000..0d3b60d2b38c
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/logical_traits/requirements/value_member_neg.cc
@@ -0,0 +1,26 @@
+// { dg-do compile { target c++17 } }
+
+#include <type_traits>
+
+// [meta.logical]/4,9: Every template type argument for which B_i::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.
+
+// { dg-error "'value' is not a member of 'A<0>'" "" { target *-*-* } 0 }
+// { dg-error "'value' is not a member of 'A<1>'" "" { target *-*-* } 0 }
+// { dg-error "invalid cast from type 'B<0>' to type 'bool'" "" { target *-*-* } 0 }
+// { dg-error "invalid cast from type 'B<1>' to type 'bool'" "" { target *-*-* } 0 }
+
+template<int> struct A { };
+template<int> struct B { static inline B value; };
+
+std::conjunction<A<0>> c1; // { dg-error "required from here" }
+std::conjunction<B<0>> c2; // { dg-error "required from here" }
+std::conjunction<std::true_type, A<1>> c3; // { dg-error "required from here" }
+std::conjunction<std::true_type, B<1>> c4; // { dg-error "required from here" }
+
+std::disjunction<A<0>> d1; // { dg-error "required from here" }
+std::disjunction<B<0>> d2; // { dg-error "required from here" }
+std::disjunction<std::false_type, A<1>> d3; // { dg-error "required from here" }
+std::disjunction<std::false_type, B<1>> d4; // { dg-error "required from here" }
--
2.54.0.rc1.54.g60f07c4f5c
>
> For __conjunction_impl we could just use __enable_if_t<_B1::value>
> which would require implicit converibility, but for __disjunction_impl
> we currently use __enable_if_t<!bool(_B1::value)> and if we change
> that to __enable_if_t<!_B1::value> it would do the wrong thing for:
>
> struct Silly {
> bool operator!() const { return false; }
> operator bool() const { return true; }
> };
> struct X {
> static constexpr Silly value{};
> };
> static_assert(std::disjunction_v<X, false_type>);
>
>
> > + };
> >
> > template<>
> > struct conjunction<>
> > @@ -254,7 +261,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > template<typename... _Bn>
> > struct disjunction
> > : __detail::__disjunction_impl<void, _Bn...>::type
> > - { };
> > + {
> > + static_assert(is_convertible<decltype(__detail::__disjunction_impl<void, _Bn...>
> > + ::type::value), bool>::value,
> > + "result type's value is not convertible to bool");
> > + };
> >
> > template<>
> > struct disjunction<>
> > 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 0000000000000..eb0f246367f11
> > --- /dev/null
> > +++ b/libstdc++-v3/testsuite/20_util/logical_traits/requirements/junction_neg.cc
> > @@ -0,0 +1,27 @@
> > +// { dg-do compile { target c++17 } }
> > +
> > +#include <type_traits>
> > +
> > +class S {}; // not convertible to bool
> > +
> > +struct T {
> > + 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, T> 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, T> dfTtf; // { dg-error "here" }
> > +std::disjunction<std::true_type, T> dtTtf;
> > +
> > +// { dg-prune-output "not convertible to bool|evaluates to false" }
> >
>
>
More information about the Libstdc++
mailing list