On Wed, Mar 12, 2025 at 9:23 AM Jonathan Wakely <jwak...@redhat.com> wrote:
> On Wed, 12 Mar 2025 at 07:59, Tomasz Kaminski <tkami...@redhat.com> wrote: > > > > > > > > On Tue, Mar 11, 2025 at 11:46 PM Jonathan Wakely <jwak...@redhat.com> > wrote: > >> > >> This change makes the consteval function slightly faster to compile. > >> Instead of keeping the counts in an array and looping over that array, > >> we can just keep a sum of how many valid types are present, and check > >> that it equals the total number of types in the pack. We can also avoid > >> doing the check entirely for the common cases where the call comes from > >> check_dynamic_spec_integer or check_dynamic_spec_string, because those > >> always use valid lists of types. > >> > >> The diagnostic is slightly worse now, because there's only a single > >> "invalid template argument types" string that appears in the output, > >> where previously we had either "non-unique template argument type" or > >> "disallowed template argument type" depending on the failure mode. > >> > >> Given that most users will never use this function directly, and > >> probably won't use invalid types anyway, the inferior diagnostic seems > >> acceptable. > >> > >> libstdc++-v3/ChangeLog: > >> > >> * include/std/format > (basic_format_parse_context::__check_types): > >> New variable template and partial specializations. > >> (basic_format_parse_context::__check_dynamic_spec_types): > >> Simplify for faster compilation. > >> (basic_format_parse_context::check_dynamic_spec): Only use > >> __check_dynamic_spec_types when __check_types is true. Use it as > >> the condition for a constexpr if statement instead of as the > >> initializer for a constexpr variable. > >> (basic_format_parse_context::check_dynamic_spec_string): Use > >> _CharT instead of char_type consistently. > >> --- > >> > >> Tested x86_64-linux. > >> > >> libstdc++-v3/include/std/format | 81 +++++++++++++++++++-------------- > >> 1 file changed, 47 insertions(+), 34 deletions(-) > >> > >> diff --git a/libstdc++-v3/include/std/format > b/libstdc++-v3/include/std/format > >> index 0d6cc7f6bef..6269cbb80e6 100644 > >> --- a/libstdc++-v3/include/std/format > >> +++ b/libstdc++-v3/include/std/format > >> @@ -305,41 +305,51 @@ namespace __format > >> constexpr void > >> check_dynamic_spec_string(size_t __id) noexcept > >> { > >> - check_dynamic_spec<const char_type*, > basic_string_view<_CharT>>(__id); > >> + check_dynamic_spec<const _CharT*, > basic_string_view<_CharT>>(__id); > >> } > >> > >> private: > >> - // Check the Mandates: condition for check_dynamic_spec<Ts...>(n) > >> + template<typename _Tp, typename... _Ts> > >> + static constexpr bool __once = (is_same_v<_Tp, _Ts> + ...) == 1; > >> + > >> + // True if we need to call __check_dynamic_spec_types for the > pack Ts > >> + template<typename... _Ts> > >> + static constexpr bool __check_types = sizeof...(_Ts) > 0; > >> + // The pack used by check_dynamic_spec_integral is valid, don't > check it. > >> + // FIXME: simplify these when PR c++/85282 is supported. > >> + template<typename _Tp> > >> + static constexpr bool > >> + __check_types<_Tp, unsigned, long long, unsigned long long> > >> + = ! is_same_v<_Tp, int>; > > > > These seem to produce wrong answer (false) if the user called > check_dynamic_spec<basic_string_view<_CharT>, unsigned, long long, unsigned > long long>, > > which is a valid set of types. > > The variable template __check_types means "do we need to check the > types", so false is the correct answer here. We do not need to check > the types for this list of types, because we know it's a valid set of > types. > > The variable template is used to decide whether to call > __check_dynamic_spec_types or to skip calling it. > Any particular reason to use this as a solution, as compared to alternatives with implementation defined function? I find the latter much more understandable, and while this allows us to avoid checking if user specified types in exactly same order, passing <basic_string_view, char> to check_dynamic_spec will still perform checking. > > >> > >> + // The pack used by check_dynamic_spec_string is valid, don't > check it. > >> + template<typename _Tp> > >> + static constexpr bool > >> + __check_types<_Tp, basic_string_view<_CharT>> > >> + = ! is_same_v<const _CharT*, _Tp>; > > > > As above check_dynamic_spec<int, basic_string_view<_CharT>>. > > I think much batter solution would be to have > __check_dynamic_spec<Ts...> private function, > > that would not perform the check at all and will be called without > checking from check_dynamic_spec_integrral, e.t.c. > > And in check_dynamic_spec, we would do > __check_dynamic_spec_types<_Ts...>() and sizeof..(Ts) > 0, before > > calling __check_dynamic_spec. > > Yes I considered that, and that would also work. > > >> + > >> + // Check the Mandates: conditions for > check_dynamic_spec<Ts...>(n) > >> template<typename... _Ts> > >> static consteval bool > >> __check_dynamic_spec_types() > >> { > >> - if constexpr (sizeof...(_Ts)) > >> - { > >> - int __counts[] = { > >> - (is_same_v<bool, _Ts> + ...), > >> - (is_same_v<_CharT, _Ts> + ...), > >> - (is_same_v<int, _Ts> + ...), > >> - (is_same_v<unsigned, _Ts> + ...), > >> - (is_same_v<long long, _Ts> + ...), > >> - (is_same_v<unsigned long long, _Ts> + ...), > >> - (is_same_v<float, _Ts> + ...), > >> - (is_same_v<double, _Ts> + ...), > >> - (is_same_v<long double, _Ts> + ...), > >> - (is_same_v<const _CharT*, _Ts> + ...), > >> - (is_same_v<basic_string_view<_CharT>, _Ts> + ...), > >> - (is_same_v<const void*, _Ts> + ...) > >> - }; > >> - int __sum = 0; > >> - for (int __c : __counts) > >> - { > >> - __sum += __c; > >> - if (__c > 1) > >> - __invalid_dynamic_spec("non-unique template > argument type"); > >> - } > >> - if (__sum != sizeof...(_Ts)) > >> - __invalid_dynamic_spec("disallowed template argument > type"); > >> - } > >> + // The types in Ts... are unique. Each type in Ts... is one of > >> + // bool, char_type, int, unsigned int, long long int, > >> + // unsigned long long int, float, double, long double, > >> + // const char_type*, basic_string_view<char_type>, or const > void*. > >> + unsigned __sum = __once<bool, _Ts...> > >> + + __once<char_type, _Ts...> > >> + + __once<int, _Ts...> > >> + + __once<unsigned int, _Ts...> > >> + + __once<long long int, _Ts...> > >> + + __once<unsigned long long int, _Ts...> > >> + + __once<float, _Ts...> > >> + + __once<double, _Ts...> > >> + + __once<long double, _Ts...> > >> + + __once<const char_type*, _Ts...> > >> + + __once<basic_string_view<char_type>, _Ts...> > >> + + __once<const void*, _Ts...>; > >> + if (__sum != sizeof...(_Ts)) > >> + __invalid_dynamic_spec("invalid template argument types"); > >> return true; > >> } > >> > >> @@ -4341,12 +4351,15 @@ namespace __format > >> // _GLIBCXX_RESOLVE_LIB_DEFECTS > >> // 4142. check_dynamic_spec should require at least one type > >> static_assert(sizeof...(_Ts) >= 1); > >> - // This call enforces the Mandates: condition that _Ts contains > valid > >> - // types and each type appears at most once. It could be a > static_assert > >> - // but this way failures give better diagnostics, due to calling > the > >> - // non-constexpr __invalid_dynamic_spec function. > >> - [[maybe_unused]] > >> - constexpr bool __ok = __check_dynamic_spec_types<_Ts...>(); > >> + > >> + if constexpr (__check_types<_Ts...>) > >> + if constexpr (__check_dynamic_spec_types<_Ts...>()) > >> + { > >> + // The call above enforces the Mandates: condition that _Ts > >> + // contains valid types and each type appears at most once. > >> + // Checking it in `if constexpr` means that failures give > better > >> + // diagnostics than if we used it as a static_assert > condition. > >> + } > >> > >> if consteval { > >> if (__id >= _M_num_args) > >> -- > >> 2.48.1 > >> > >