-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Relax and optimize all_of_t/any_of_t #554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
In the case of There is a good reason for this requirement in The performance difference between the implementations is definitely significant (and can be specifically measured by something like metabench). It can be argued that pybind11 only uses these metafunctions with a small number of arguments, but the total usage is multiplied by the number of class/function bindings which can be significant. So I would advocate for going with the faster solution over the future standard in this case. Some numbersI don't want to bikeshed more than I already have, but just to show the numbers for the above claims. The following was measured using metabench. Our current implementation could even be improved. The
template<bool...> struct bools {};
template<class> using always_true = std::true_type;
template<template<class> class Predicate, class... Ts>
using all_of_t = std::is_same<bools<Predicate<Ts>::value...>,
bools<always_true<Ts>::value...>>;
template<bool...> struct bools {};
template<template<class> class Predicate, class... Ts>
using all_of_t = std::is_same<bools<Predicate<Ts>::value..., true>,
bools<true, Predicate<Ts>::value...>>; |
That's a nifty analysis! Another very minor point: I find current our naming scheme much more readable than "conjunction" and "disjunction" which is fairly uncommon vocabulary :) |
I will restore all_of_t in that case. I think, however, that conjunction provides some coding usefulness still (eg when testing multiple predicates rather than multiple types through the same predicate). It's also supported natively by MSVC, so we could use the faster version for most things with a conjunction-based fallback for MSVC. |
I also agree on the names. |
@dean0x7d - what's an efficient any_of_t version? (It doesn't need to work in MSVC). |
Yeah,
It's only supported since Update 2, so it doesn't help much if it would require 2 fallbacks.
Mirroring template<template<class> class Predicate, class... Ts>
using any_of_t = negation<std::is_same<bools<Predicate<Ts>::value..., false>,
bools<false, Predicate<Ts>::value...>>>; |
What is pybind's MSVC support? I thought it was latest-update of MSVC 15, not any MSVC 15. And if not, can we change it for 2.0? (@wjakob) |
I think the offical requirement reads "Microsoft Visual Studio 2015 or newer", but it is actually "Microsoft Visual Studio 2015 SP3 or newer". Is there any reason to make changes in addition to that? |
Is SP3 the same as Update 3? |
yeah that's what I meant |
Okay, just checking (not well versed in the MS lingo). No changes needed, Update 3 is fine, it has the needed std::conjunction/disjunction. |
d92fd4a
to
8803916
Compare
Redid this. This is replacing @dean0x7d - I went with Comments? |
I honestly don't think transformations of this form are really worth it: // before
enable_if_t<Predicate1<T>::value && !Predicate2<T>::value>
// after
enable_if_t<all_of<Predicate1<T>, negation<Predicate2<T>>>::value> At that point enable_if_t<Predicate1<T>() && !Predicate2<T>()> which is IMO the cleanest syntax. Having the |
constexpr auto is_rvalue = !std::is_pointer<Return>::value | ||
&& !std::is_lvalue_reference<Return>::value; | ||
constexpr auto is_rvalue = detail::none_of<std::is_pointer<Return>, | ||
std::is_lvalue_reference<Return>>::value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also just adds a template instantiation to what was a constant expression. The result is not faster or shorter. I'm not sure if there is any readability advantage over the logical operators.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I'll remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though I wonder if it wouldn't be better replaced with: constexpr auto is_rvalue = !detail::move_never<Return>::value;
This replaces the current `all_of_t<Pred, Ts...>` with `all_of<Ts...>`, with previous use of `all_of_t<Pred, Ts...>` becoming `all_of<Pred<Ts>...>` (and similarly for `any_of_t`). It also adds a `none_of<Ts...>`, a shortcut for `negation<any_of<Ts...>>`. This allows `all_of` and `any_of` to be used a bit more flexible, e.g. in cases where several predicates need to be tested for the same type instead of the same predicate for multiple types. This commit replaces the implementation with a more efficient version for non-MSVC. For MSVC, this changes the workaround to use the built-in, recursive std::conjunction/std::disjunction instead. This also removes the `count_t` since `any_of_t` and `all_of_t` were the only things using it. This commit also rearranges some of the future std imports to use actual `std` implementations for C++14/17 features when under the appropriate compiler mode, as we were already doing for a few things (like index_sequence). Most of these aren't saving much (the implementation for enable_if_t, for example, is trivial), but I think it makes the intention of the code instantly clear. It also enables MSVC's native std::index_sequence support.
ec5f750
to
7e0d3eb
Compare
I removed the conversions that, as @dean0x7d pointed out, are really not saving much, and only kept ones that offer a decent coding/clarity contribution. |
I also removed the gcc6/C++17 compilation test from this PR in favour of a gcc7/C++17 test in #555. |
@dean0x7d - I agree that |
Merged, thank you! |
We can't support this for classes from imported modules (which is the primary purpose of a ctor argument base class) because we *have* to have both parent and derived to properly extract a multiple-inheritance base class pointer from a derived class pointer. We could support this for actual `class_<Base, ...> instances, but since in that case the `Base` is already present in the code, it seems more consistent to simply always require MI to go via template options. This also adds a new "number_of" template, mostly based on the `count_t` removed in PR pybind#554, to count the number of satisfied templates; this is basically just `constexpr_sum`, and uses that except for under MSVC which doesn't think the constexpr is a constexpr.
We can't support this for classes from imported modules (which is the primary purpose of a ctor argument base class) because we *have* to have both parent and derived to properly extract a multiple-inheritance base class pointer from a derived class pointer. We could support this for actual `class_<Base, ...> instances, but since in that case the `Base` is already present in the code, it seems more consistent to simply always require MI to go via template options. This also adds a new "number_of" template, mostly based on the `count_t` removed in PR pybind#554, to count the number of satisfied templates; this is basically just `constexpr_sum`, and uses that except for under MSVC which doesn't think the constexpr is a constexpr.
We can't support this for classes from imported modules (which is the primary purpose of a ctor argument base class) because we *have* to have both parent and derived to properly extract a multiple-inheritance base class pointer from a derived class pointer. We could support this for actual `class_<Base, ...> instances, but since in that case the `Base` is already present in the code, it seems more consistent to simply always require MI to go via template options. This also adds a new "number_of" template, mostly based on the `count_t` removed in PR pybind#554, to count the number of satisfied templates; this is basically just `constexpr_sum`, and uses that except for under MSVC which doesn't think the constexpr is a constexpr.
C++17 has
std::conjunction
/disjunction
which are quite similar in purpose to ourall_of_t
andany_of_t
in practice. (They can differ if you pass in a Predicate with a non-bool ::value, but we don't do that). Essentiallydetail::all_of_t<P, T...>
is equivalent tostd::conjunction<P<T>...>
, andany_of_t<P, T...>
corresponds tostd::disjunction<P<T>...>
.Unlike
any
/all_of_t
,dis
/conjunction
aren't tied to a single predicate, so it can be used as a compile-timeand
across multiple types with::value
s, allowing some less verbose code in various circumstances.This commit converts
all_of_t
/any_of_t
intoconjunction
/disjunction
calls, and uses conjunction/disjunction in a few other places for some code cleanups.I also removed the
count_t
sinceany_of_t
andall_of_t
was the only thing using it.This commit also uses aliases to the
std
implementations for C++14/17 features when under the appropriate compiler mode, as we were already doing for a few things (like index_sequence). Most of these aren't saving much (the implementation for enable_if_t, for example, is trivial), but I think it makes the intention of the code instantly clear.Since this introduces a slightly different implementation when compiling under C++17, I've also added a travis-ci build to compile in C++17 mode under gcc 6. (I tried adding an experimental build with a recent gcc 7 snapshot, but pybind triggers an ICE in c++17 mode with a current g++ snapshot.)