On Fri, 24 Mar 2023 at 09:58, Jonathan Wakely <jwakely....@gmail.com> wrote:
>
> On Fri, 24 Mar 2023 at 07:10, Ken Matsui via Gcc <gcc@gcc.gnu.org> wrote:
> >
> > Hi,
> >
> > I am working on the GSoC project, "C++: Implement compiler built-in
> > traits for the standard library traits". I found the following library
> > traits that I am not sure if implementing built-in traits brings
> > reasonable speed up.
> >
> > * std::is_fundamental
> > * std::is_arithmetic
> > * std::is_scalar
> > * std::is_object
> > * std::is_compound
> > * std::is_scoped_enum
> >
> > For example, std::is_object has no template specializations, but its
> > inheriting class looks complicated.
> >
> > __not_<__or_<is_function<_Tp>, is_reference<_Tp>, is_void<_Tp>>>::type
> >
> > If we define the built-in trait for this trait, we have: (as
> > equivalence of the above code)
> >
> > __bool_constant<__is_object(_Tp)>
> >
> > And __is_object built-in trait should be like:
> >
> > !(type1 == FUNCTION_TYPE || type1 == ...)
> >
> > In this case, could someone tell me which one would be faster? Or, is
> > there no other way to know which but to benchmark?
>
> You should benchmark it anyway, I was always expecting that to be a
> part of this GSoC project :-)
>
> But is_object is NOT a "relatively simple" trait. What you show above
> is very complex. One of the more complex traits we have. Partial
> specializations are quite fast to match (in general) so eliminating
> partial speclializations (e.g. like we have for is_const) should not
> be the goal.
>
> For is_object<const int> we instantiate:
>
> is_function<const int>
> is_const<const int>
> is_reference<const int>
> is_void<const int>
> __bool_constant<false>
> __or_<false_type, false_type, false_type>
> __not_<false_type>
> __bool_constant<true>
>
> This is a ton of work! Instantiating class templates is the slowest
> part of trait evaluation, not matching partial specializations.

And then if the same program also instantiates is_object<int> (without
the const), then currently we instantiate:

is_function<int>
is_const<int>
is_reference<int>
is_void<int>
__bool_constant<false>
__or_<false_type, false_type, false_type>
__not_<false_type>
__bool_constant<true>

The first four instantiations are not shared with is_object<const
int>, so we have to instantiate them anew. The last four are common
with is_object<const int> so don't need to be instantiated again, the
compiler will have cached those instantiations.
But if the same program also uses is_object<long> then we have another
four new instantiations to generate. And another four for
is_object<float>. And another four for is_object<std::string> etc.
etc.

With a built-in they will all use __bool_constant<true> and nothing
else (apart from the top-level is_object<T> specialization itself,
which is unavoidable* since that's the trait actually being
evaluated).

* In fact, it's not really unavoidable, MSVC avoids it entirely. Their
compiler pattern matches the standard traits and never even
instantiates them, so every use of is_object<T>::value gets expanded
directly to __is_object(T) without instantiating anything. We don't do
that, and if we just replace turn 8 or 9 class template instantiations
into 1 then we'll be doing great.

Reply via email to