[PATCH] libstdc++: check value in std::{con,dis}junction
Patrick Palka
ppalka@redhat.com
Wed Apr 29 23:30:06 GMT 2026
On Wed, 29 Apr 2026, Patrick Palka 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.
>
> 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.
Also operands of ||, && and ! need to be contextually convertible to
bool in the language which means explicit convertibility IIUC. So
naturally the type-level dis/conjunction should also only require
explicit convertibility.
>
> 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