[PATCH] libstdc++: check value in std::{con,dis}junction
Jonathan Wakely
jwakely@redhat.com
Thu Apr 30 09:23:32 GMT 2026
On Thu, 30 Apr 2026 at 00:10, Patrick Palka <ppalka@redhat.com> wrote:
>
> 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.
[meta.logical] p5 is clear that the actual implementation needs to use
explicit conversion (which we do since you refactored it in GCC 13).
[meta.logical] p4 says that Bi::value "shall" be convertible to bool.
That's just a precondition, so it's the user's job to get that right
and it's UB if they don't get it right. So we don't actually need to
diagnose anything here for conformance.
There's no requirement to make the new testcase ill-formed, so I'm
not sure we need to change anything.
>
> 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