RFR: 8251274: Provide utility types for function SFINAE using extra template parameters
Erik Österlund
erik.osterlund at oracle.com
Thu Aug 27 08:24:41 UTC 2020
Hi Kim,
On 2020-08-27 07:40, Kim Barrett wrote:
>> On Aug 26, 2020, at 11:42 AM, Erik Österlund <erik.osterlund at oracle.com> wrote:
>>
>> Hi Kim,
>>
>> I have one comment on this. IMO using value or type based SFINAE is mostly a matter of preference. Either you prefer passing in trait types, or you prefer passing in constexpr booleans. The constexpr boolean approach seems to be strictly more useful IMO, because you can pass in evaluated constexpr functions, which is simply not possible in type based SFINAE. I have for example a situation where I have VM_Version support for a feature being returned in a constexpr function, that I can use to SFINAE away platform code on unsupported platforms (actual use case for me).
> IIt's easy to bounce back and forth using things like std::integral_constant.
> (One could even go the route of Boost.Hana for that sort of thing. But I
> don't think we are likely to be doing enough metaprogramming to make
> implementing that level of infrastructure worthwhile.) So I dispute the
> suggestion that one is more useful than the other.
It is also easy to write ::value when passing in a type. BTW what I
meant with one style being more "useful" than the other... what I really
meant to say is that the value SFINAE is strictly more powerful than
type SFINAE in expressiveness. Specifically, you can express all type
SFINAE with value SFINAE by just appending "::value" to the type trait
you pass in. Conversely, you can not express all useages of value SFINAE
with type SFINAE, due to constexpr. This makes value SFINAE strictly
more powerful in expressiveness. Unless you write the whole constexpr
function body as a composition of type traits of course... which I hope
we won't do... but we kind of actually do in some places as we didn't
have constexpr available yet. So if we are to have a convention using
only one of the two, value SFINAE is the more desirable alternative.
>> Therefore, instead of having both EnableIfX and EnableIfT, I wonder if we could just stick to one convention instead, and say we pass in booleans, not types wrapping booleans. That way, there will never be any question as to which convention is being used or which one you should use - we use only one, and simply convert types to booleans before passing in to EnableIf. And then we don't need to have multiple EnableIf macros to differentiate the two cases. We just stick to one by convention and say this is the way we do things. Today we do not to my knowledge use any type SFINAE in HotSpot, it's all value based (the parameter to EnableIf is a bool, not a type). So it would further more seem that is the convention we already have today, and I think is the convention we want to keep on using, especially due to constexpr making that choice strictly more useful.
> We could do that. Indeed, the Standard only provides std::enable_if (and
> friends) that takes a bool condition.
Right. And that is my preference too.
> However, I think type-based SFINAE is common and likely to remain so because
> that's the form the type traits currently come in. (Though C++17 adds
> template variables for most or all of the trait types. We could perhaps
> provide our own until we can use C++17.)
>
> I find the frequent `::value` suffixes intrusive and annoying. (The
> alternative of constructing the trait type is perhaps less annoying, i.e.
> EnableIfX<std::is_integral<T>()>. Maybe that's good enough. It's also the
> same number of characters as the C++17 template variables, though less
> useful for additional calculations (unless going Boost.Hana style).)
Right. So on the one hand side you find it annoying to type "::value"
when passing in a type trait. But on the other hand, having both
EnableIfX and EnableIfT, and forcing readers, writers and reviewers to
figure out which one is used or should be used, has the following also
annoying properties:
* There is no technical need for EnableIfT, so I don't see why we need
the extra confusion about using EnableIfT vs EnableIfX. And I think
SFINAE code does not necessarily need to become more confusing than it
already is to readers in HotSpot.
* Makes us not have a convention any more. Code will be written
inconsistently sometimes randomly passing in a bool by some programmers,
and with a type for other programmers, based on stylistic preferences
alone, as opposed to different technical needs. I think that kind of
stylistic choices only leads to a soup of different code styles and
inconsistencies that didn't need to be there if we could just agree upon
one convention instead. Such things tend to also introduce meaningless
discussion points and hence stalls in review threads, where people argue
that this or that style should be used due to different consistency vs
style preference arguments and opinions. With a single convention, such
debates are more easily shut down.
* If we are suddenly allowing type SFINAE as well, purely because we
think it stylistically looks better, then let's discuss style. I think
having to write EnableIfX instead of EnableIf (or similarly ENABLE_IF_X
instead of ENABLE_IF) is stylistically ugly. So if anything, having both
type and value SFINAE, with one macro or trait for each variant, is not
an obvious stylistic improvement to me. Quite the opposite in fact.
> As I hinted in the RFR email, I tried to have one EnableIf-style API that
> handles an argument that can be either a trait type or a constexpr bool
> value. Something like
>
> template<typename T, ENABLE_IF(std::is_integral<T>)>
> void foo(T x) { ... }
>
> template<typename T1, typename T2, ENABLE_IF(sizeof(T1) == sizeof(T2))>
> void foo(T1 x, T2 y) { ... }
>
> Unfortunately, all my attempts at that tripped over MSVC bugs. And my bug
> report was closed as won't fix.
>
> (gcc and clang worked fine for the most part. One of the many variants I
> tried (while failing to get past MSVC) ICE'd gcc, which I reported and was
> accepted as an ice-on-valid-code regression.)
Yeah. I think that having ENABLE_IF accept both types and values would
be less confusing compared to having EnableIfX vs EnableIfT. But I think
the even less confusing solution is to have an ENABLE_IF that only
accepts bools, and make that the convention explicitly on the call site.
So your example:
template<typename T, ENABLE_IF(std::is_integral<T>)>
void foo(T x) { ... }
would just have to adapt to the value SFINAE convention and do:
template<typename T, ENABLE_IF(std::is_integral<T>::value)>
void foo(T x) { ... }
Suddenly there is no need for ENABLE_IF to support both booleans and
types, and there is a clear convention how to do SFINAE: with values.
If the argument is that it is not pretty to have to write "::value",
then I think the same stylistic comment can be said about having to
append "T" to "EnableIf" to declare that you have this or that type of
EnableIf that either will do the "::value" conversion for you internally
or not.
>> So basically, my feedback is that I think you can safely remove EnableIfT and rename EnableIfX to EnableIf instead.
> Not while we still have (currently ~100) uses of our (perhaps legacy)
> EnableIf, which is significantly different. I'm not interested in developing
> or reviewing a change of all the existing EnableIf for function templates to
> use the new additional template parameter mechanism in one changeset.
While I think having a legacy style that we are telling people not to
use is unfortunate, I do understand that such a cleanup is a larger
undertaking. So if the concern is that the identifier "EnableIf" is
already taken today, then I think you can do what your previous example
did: call the macro variant used inside of the template arguments
ENABLE_IF instead, and have it take values only. Then we can transition
away from using now "legacy" EnableIf gradually. And seeing as it is a
macro, using capital letters is the natural name anyway.
Hope this all makes sense.
Thanks,
/Erik
More information about the hotspot-dev
mailing list