From 1a53966f5ce8ca5542d3612c780ea4b69be34de2 Mon Sep 17 00:00:00 2001 From: Dario Izzo Date: Thu, 20 Oct 2022 11:16:55 +0200 Subject: [PATCH] removing _format literal --- include/obake/kpack.hpp | 134 ++-- .../obake/polynomials/d_packed_monomial.hpp | 49 +- include/obake/polynomials/packed_monomial.hpp | 33 +- include/obake/power_series/power_series.hpp | 128 ++-- include/obake/series.hpp | 689 +++++++++--------- src/polynomials/packed_monomial.cpp | 20 +- 6 files changed, 535 insertions(+), 518 deletions(-) diff --git a/include/obake/kpack.hpp b/include/obake/kpack.hpp index e69c64bc..d8633627 100644 --- a/include/obake/kpack.hpp +++ b/include/obake/kpack.hpp @@ -157,61 +157,61 @@ concept kpackable = is_kpackable_v; namespace detail { - // Various helpers to fetch kpack data. - - // Return the max packable size for a given type. This is - // the size of the deltas, lims and klims arrays. - // NOTE: unsigned conversion is ok as the array sizes - // are always a fraction of a bit size. - template - constexpr unsigned kpack_max_size() - { - auto ret = ::std::size(kpack_data::deltas); +// Various helpers to fetch kpack data. - assert(ret == ::std::size(kpack_data::lims)); - assert(ret == ::std::size(kpack_data::klims)); +// Return the max packable size for a given type. This is +// the size of the deltas, lims and klims arrays. +// NOTE: unsigned conversion is ok as the array sizes +// are always a fraction of a bit size. +template +constexpr unsigned kpack_max_size() +{ + auto ret = ::std::size(kpack_data::deltas); - return static_cast(ret); - } + assert(ret == ::std::size(kpack_data::lims)); + assert(ret == ::std::size(kpack_data::klims)); - // Return the delta for a given size. - template - inline T kpack_get_delta(unsigned size) - { - assert(size > 0u && size <= detail::kpack_max_size()); + return static_cast(ret); +} - return kpack_data::deltas[size - 1u]; - } +// Return the delta for a given size. +template +inline T kpack_get_delta(unsigned size) +{ + assert(size > 0u && size <= detail::kpack_max_size()); - // Return the components' limits for a given size. - template - inline ::std::pair kpack_get_lims(unsigned size) - { - assert(size > 0u && size <= detail::kpack_max_size()); + return kpack_data::deltas[size - 1u]; +} - const auto lim = kpack_data::lims[size - 1u]; +// Return the components' limits for a given size. +template +inline ::std::pair kpack_get_lims(unsigned size) +{ + assert(size > 0u && size <= detail::kpack_max_size()); - if constexpr (is_signed_v) { - return ::std::pair{-lim, lim}; - } else { - return ::std::pair{T(0), lim}; - } + const auto lim = kpack_data::lims[size - 1u]; + + if constexpr (is_signed_v) { + return ::std::pair{-lim, lim}; + } else { + return ::std::pair{T(0), lim}; } +} - // Return the coded values' limits for a given size. - template - inline ::std::pair kpack_get_klims(unsigned size) - { - assert(size > 0u && size <= detail::kpack_max_size()); +// Return the coded values' limits for a given size. +template +inline ::std::pair kpack_get_klims(unsigned size) +{ + assert(size > 0u && size <= detail::kpack_max_size()); - const auto klim = kpack_data::klims[size - 1u]; + const auto klim = kpack_data::klims[size - 1u]; - if constexpr (is_signed_v) { - return ::std::pair{-klim, klim}; - } else { - return ::std::pair{T(0), klim}; - } + if constexpr (is_signed_v) { + return ::std::pair{-klim, klim}; + } else { + return ::std::pair{T(0), klim}; } +} } // namespace detail @@ -232,9 +232,10 @@ class kpacker using namespace ::fmt::literals; obake_throw(::std::overflow_error, - "Invalid size specified in the constructor of a Kronecker packer for " - "the type '{}': the maximum possible size is {}, but a size of {} " - "was specified instead"_format(::obake::type_name(), detail::kpack_max_size(), size)); + fmt::format("Invalid size specified in the constructor of a Kronecker packer for " + "the type '{}': the maximum possible size is {}, but a size of {} " + "was specified instead", + ::obake::type_name(), detail::kpack_max_size(), size)); } } @@ -244,9 +245,10 @@ class kpacker using namespace ::fmt::literals; if (obake_unlikely(m_index == m_size)) { - obake_throw(::std::out_of_range, - "Cannot push any more values to this Kronecker packer for the type '{}': the number of " - "values already pushed to the packer is equal to the packer's size ({})"_format( + obake_throw( + ::std::out_of_range, + fmt::format("Cannot push any more values to this Kronecker packer for the type '{}': the number of " + "values already pushed to the packer is equal to the packer's size ({})", ::obake::type_name(), m_size)); } @@ -255,9 +257,10 @@ class kpacker // Check that n is within the limits. if (obake_unlikely(n < lim_min || n > lim_max)) { - obake_throw(::std::overflow_error, "Cannot push the value {} to this Kronecker packer for the type " - "'{}': the value is outside the allowed range [{}, {}]"_format( - n, ::obake::type_name(), lim_min, lim_max)); + obake_throw(::std::overflow_error, + fmt::format("Cannot push the value {} to this Kronecker packer for the type " + "'{}': the value is outside the allowed range [{}, {}]", + n, ::obake::type_name(), lim_min, lim_max)); } // Do the encoding. @@ -297,15 +300,18 @@ class kunpacker if (size == 0u) { if (obake_unlikely(n != T(0))) { obake_throw(::std::invalid_argument, - "Only a value of zero can be used in a Kronecker unpacker " - "with a size of zero, but a value of {} was provided instead"_format(n)); + fmt::format("Only a value of zero can be used in a Kronecker unpacker " + "with a size of zero, but a value of {} was provided instead", + n)); } } else { if (obake_unlikely(size > detail::kpack_max_size())) { - obake_throw(::std::overflow_error, - "Invalid size specified in the constructor of a Kronecker unpacker for the type '{}': the " - "maximum possible size is {}, but a size of {} was specified instead"_format( - ::obake::type_name(), detail::kpack_max_size(), size)); + obake_throw( + ::std::overflow_error, + fmt::format( + "Invalid size specified in the constructor of a Kronecker unpacker for the type '{}': the " + "maximum possible size is {}, but a size of {} was specified instead", + ::obake::type_name(), detail::kpack_max_size(), size)); } // Get the coded value's limits. @@ -313,9 +319,10 @@ class kunpacker // Check that n is within the limits. if (obake_unlikely(n < klim_min || n > klim_max)) { - obake_throw(::std::overflow_error, "The value {} passed to a Kronecker unpacker for the type " - "'{}' is outside the allowed range [{}, {}]"_format( - n, ::obake::type_name(), klim_min, klim_max)); + obake_throw(::std::overflow_error, + fmt::format("The value {} passed to a Kronecker unpacker for the type " + "'{}' is outside the allowed range [{}, {}]", + n, ::obake::type_name(), klim_min, klim_max)); } } } @@ -326,8 +333,9 @@ class kunpacker using namespace ::fmt::literals; obake_throw(::std::out_of_range, - "Cannot unpack any more values from this Kronecker unpacker: the number of " - "values already unpacked is equal to the unpacker's size ({})"_format(m_size)); + fmt::format("Cannot unpack any more values from this Kronecker unpacker: the number of " + "values already unpacked is equal to the unpacker's size ({})", + m_size)); } // Prepare m_cur_prod. This is the divisor in the remainder operation. diff --git a/include/obake/polynomials/d_packed_monomial.hpp b/include/obake/polynomials/d_packed_monomial.hpp index 3ca95290..5752e665 100644 --- a/include/obake/polynomials/d_packed_monomial.hpp +++ b/include/obake/polynomials/d_packed_monomial.hpp @@ -83,7 +83,7 @@ inline constexpr unsigned dpm_max_psize = ::obake::detail::kpack_max_size(); // Dynamic packed monomial. template - requires(PSize > 0u) && (PSize <= dpm_max_psize)class d_packed_monomial +requires(PSize > 0u) && (PSize <= dpm_max_psize)class d_packed_monomial { friend class ::boost::serialization::access; @@ -109,9 +109,8 @@ template // Constructor from input iterator and size. template - requires InputIterator && - SafelyCastable::reference, T> explicit d_packed_monomial(It it, - ::std::size_t n) + requires InputIterator && SafelyCastable::reference, T> + explicit d_packed_monomial(It it, ::std::size_t n) // LCOV_EXCL_START : m_container( ::obake::safe_cast(detail::dpm_n_expos_to_vsize(n)), @@ -158,17 +157,13 @@ template public: // Ctor from a pair of input iterators. template - requires InputIterator && - SafelyCastable::reference, T> explicit d_packed_monomial(It b, It e) - : d_packed_monomial(input_it_ctor_tag{}, b, e) - { - } + requires InputIterator && SafelyCastable::reference, T> + explicit d_packed_monomial(It b, It e) : d_packed_monomial(input_it_ctor_tag{}, b, e) {} // Ctor from input range. template - requires InputRange && - SafelyCastable>::reference, T> explicit d_packed_monomial( - Range &&r) + requires InputRange && SafelyCastable>::reference, T> + explicit d_packed_monomial(Range &&r) : d_packed_monomial(input_it_ctor_tag{}, ::obake::begin(::std::forward(r)), ::obake::end(::std::forward(r))) { @@ -176,7 +171,8 @@ template // Ctor from init list. template - requires SafelyCastable explicit d_packed_monomial(::std::initializer_list l) + requires SafelyCastable + explicit d_packed_monomial(::std::initializer_list l) : d_packed_monomial(input_it_ctor_tag{}, l.begin(), l.end()) { } @@ -363,7 +359,7 @@ inline void key_stream_insert(::std::ostream &os, const d_packed_monomial::v // is worth it to change the safe arithmetics API // for this. template -requires InputRange &&InputRange &&detail::same_d_packed_monomial_v< +requires InputRange && InputRange && detail::same_d_packed_monomial_v< remove_cvref_t>::reference>, - remove_cvref_t>::reference>> inline bool -monomial_range_overflow_check(R1 &&r1, R2 &&r2, const symbol_set &ss) + remove_cvref_t>::reference>> +inline bool monomial_range_overflow_check(R1 &&r1, R2 &&r2, const symbol_set &ss) { using pm_t = remove_cvref_t>::reference>; using value_type = typename pm_t::value_type; @@ -990,8 +986,10 @@ inline d_packed_monomial monomial_pow(const d_packed_monomial) { // Provide better error message if U is ostreamable. using namespace ::fmt::literals; - obake_throw(::std::invalid_argument, "Invalid exponent for monomial exponentiation: the exponent " - "({}) cannot be converted into an integral value"_format(n)); + obake_throw(::std::invalid_argument, + fmt::format("Invalid exponent for monomial exponentiation: the exponent " + "({}) cannot be converted into an integral value", + n)); } else { obake_throw(::std::invalid_argument, "Invalid exponent for monomial exponentiation: the exponent " "cannot be converted into an integral value"); @@ -1376,8 +1374,9 @@ inline ::std::pair> monomial_integrate(const d_pa obake_throw( ::std::domain_error, - "Cannot integrate a dynamic packed monomial: the exponent of the integration variable " - "('{}') is -1, and the integration would generate a logarithmic term"_format( + fmt::format( + "Cannot integrate a dynamic packed monomial: the exponent of the integration variable " + "('{}') is -1, and the integration would generate a logarithmic term", *ss.nth(static_cast(i)))); } } diff --git a/include/obake/polynomials/packed_monomial.hpp b/include/obake/polynomials/packed_monomial.hpp index 81e7c2f1..a40c0475 100644 --- a/include/obake/polynomials/packed_monomial.hpp +++ b/include/obake/polynomials/packed_monomial.hpp @@ -73,9 +73,8 @@ class packed_monomial constexpr explicit packed_monomial(const T &n) : m_value(n) {} // Constructor from input iterator and size. template - requires InputIterator && - SafelyCastable::reference, T> constexpr explicit packed_monomial(It it, - unsigned n) + requires InputIterator && SafelyCastable::reference, T> + constexpr explicit packed_monomial(It it, unsigned n) { kpacker kp(n); for (auto i = 0u; i < n; ++i, ++it) { @@ -100,24 +99,27 @@ class packed_monomial public: // Ctor from a pair of forward iterators. template - requires ForwardIterator &&SafelyCastable::difference_type, unsigned> && - SafelyCastable::reference, T> constexpr explicit packed_monomial(It b, It e) + requires ForwardIterator && SafelyCastable < typename ::std::iterator_traits::difference_type, + unsigned + > &&SafelyCastable::reference, T> constexpr explicit packed_monomial(It b, + It e) : packed_monomial(fwd_it_ctor_tag{}, b, e) { } // Ctor from forward range. template - requires ForwardRange && - SafelyCastable>::difference_type, unsigned> && - SafelyCastable>::reference, - T> constexpr explicit packed_monomial(Range &&r) + requires ForwardRange && SafelyCastable < + typename ::std::iterator_traits>::difference_type, + unsigned > &&SafelyCastable>::reference, + T> constexpr explicit packed_monomial(Range &&r) : packed_monomial(fwd_it_ctor_tag{}, ::obake::begin(::std::forward(r)), ::obake::end(::std::forward(r))) { } // Ctor from init list. template - requires SafelyCastable constexpr explicit packed_monomial(::std::initializer_list l) + requires SafelyCastable + constexpr explicit packed_monomial(::std::initializer_list l) : packed_monomial(fwd_it_ctor_tag{}, l.begin(), l.end()) { } @@ -290,10 +292,10 @@ inline constexpr bool same_packed_monomial_v = same_packed_monomial::value // the constraints on packed_monomial), // - the random-access iterator concept. template -requires InputRange &&InputRange &&detail::same_packed_monomial_v< +requires InputRange && InputRange && detail::same_packed_monomial_v< remove_cvref_t>::reference>, - remove_cvref_t>::reference>> inline bool -monomial_range_overflow_check(R1 &&r1, R2 &&r2, const symbol_set &ss) + remove_cvref_t>::reference>> +inline bool monomial_range_overflow_check(R1 &&r1, R2 &&r2, const symbol_set &ss) { using pm_t = remove_cvref_t>::reference>; using value_type = typename pm_t::value_type; @@ -541,8 +543,9 @@ inline packed_monomial monomial_pow(const packed_monomial &p, const U &n, // Provide better error message if U is ostreamable. using namespace ::fmt::literals; obake_throw(::std::invalid_argument, - "Invalid exponent for monomial exponentiation: the exponent ({}) " - "cannot be converted into an integral value"_format(n)); + fmt::format("Invalid exponent for monomial exponentiation: the exponent ({}) " + "cannot be converted into an integral value", + n)); } else { obake_throw(::std::invalid_argument, "Invalid exponent for monomial exponentiation: the exponent " "cannot be converted into an integral value"); diff --git a/include/obake/power_series/power_series.hpp b/include/obake/power_series/power_series.hpp index 3a2e684a..dcdee491 100644 --- a/include/obake/power_series/power_series.hpp +++ b/include/obake/power_series/power_series.hpp @@ -152,8 +152,9 @@ inline void load(Archive &ar, ::obake::power_series::detail::trunc_t &t, unsi default: { using namespace ::fmt::literals; - obake_throw(::std::invalid_argument, "The deserialisation of a truncation limit for a power" - "series produced the invalid variant index {}"_format(idx)); + obake_throw(::std::invalid_argument, fmt::format("The deserialisation of a truncation limit for a power" + "series produced the invalid variant index {}", + idx)); } // LCOV_EXCL_STOP } @@ -386,10 +387,11 @@ using psk_pdeg_t = detected_t concept power_series_key - = Key &&customisation::internal::series_default_degree_type_common_reqs>::value - &&customisation::internal::series_default_degree_type_common_reqs>::value - && ::std::is_same_v, detail::psk_pdeg_t> &&Hashable &> - &&EqualityComparable &> &&StreamInsertable &>; + = Key && customisation::internal::series_default_degree_type_common_reqs>:: + value && customisation::internal::series_default_degree_type_common_reqs< + detail::psk_pdeg_t>::value && ::std::is_same_v, detail::psk_pdeg_t> && Hashable + < const detail::psk_deg_t +& > &&EqualityComparable &> &&StreamInsertable &>; // Definition of the power series class. template @@ -417,8 +419,8 @@ namespace power_series // Implementation of (partial) degree truncation for power series. template -requires LessThanComparable> inline void truncate_degree(p_series &ps, - const T &d) +requires LessThanComparable> +inline void truncate_degree(p_series &ps, const T &d) { // Use the default functor for the extraction of the term degree. // NOTE: d_impl is assured to work thanks to the concept @@ -434,8 +436,8 @@ requires LessThanComparable> inline voi } template -requires LessThanComparable> inline void -truncate_p_degree(p_series &ps, const T &d, const symbol_set &s) +requires LessThanComparable> +inline void truncate_p_degree(p_series &ps, const T &d, const symbol_set &s) { // Use the default functor for the extraction of the term degree. // NOTE: d_impl is assured to work thanks to the concept @@ -461,8 +463,8 @@ namespace detail // Set total degree truncation. template -requires SafelyCastable> inline p_series &set_truncation_impl(p_series &ps, - const T &d) +requires SafelyCastable> +inline p_series &set_truncation_impl(p_series &ps, const T &d) { // Convert safely d to the degree type. const auto deg = ::obake::safe_cast>(d); @@ -494,8 +496,8 @@ requires SafelyCastable> inline p_series & // Set partial degree truncation. template -requires SafelyCastable> inline p_series & -set_truncation_impl(p_series &ps, const T &d, symbol_set ss) +requires SafelyCastable> +inline p_series &set_truncation_impl(p_series &ps, const T &d, symbol_set ss) { // Convert safely d to the degree type. const auto deg = ::obake::safe_cast>(d); @@ -578,7 +580,8 @@ concept make_p_series_supported // Overload without a symbol set, no truncation. template -requires make_p_series_supported inline auto make_p_series_impl(const Args &...names) +requires make_p_series_supported +inline auto make_p_series_impl(const Args &...names) { auto make_p_series = [](const auto &n) { using str_t = remove_cvref_t; @@ -604,7 +607,8 @@ requires make_p_series_supported inline auto make_p_series_impl(cons // Overload with a symbol set, no truncation. template -requires make_p_series_supported inline auto make_p_series_impl(const symbol_set &ss, const Args &...names) +requires make_p_series_supported +inline auto make_p_series_impl(const symbol_set &ss, const Args &...names) { // Create a temp vector of ints which we will use to // init the keys. @@ -636,9 +640,9 @@ requires make_p_series_supported inline auto make_p_series_impl(cons if (obake_unlikely(it == ss.end() || *it != s)) { using namespace ::fmt::literals; - obake_throw(::std::invalid_argument, - "Cannot create a power series with symbol set {} from the " - "generator '{}': the generator is not in the symbol set"_format(detail::to_string(ss), s)); + obake_throw(::std::invalid_argument, fmt::format("Cannot create a power series with symbol set {} from the " + "generator '{}': the generator is not in the symbol set", + detail::to_string(ss), s)); } // Set to 1 the exponent of the corresponding generator. @@ -673,11 +677,12 @@ namespace detail template concept make_p_series_t_supported - = make_p_series_supported &&SafelyCastable>>; + = make_p_series_supported && SafelyCastable>>; // Overload without a symbol set, total truncation. template -requires make_p_series_t_supported inline auto make_p_series_t_impl(const U &d, const Args &...names) +requires make_p_series_t_supported +inline auto make_p_series_t_impl(const U &d, const Args &...names) { // Convert d to the degree type. const auto deg = ::obake::safe_cast>>(d); @@ -709,8 +714,8 @@ requires make_p_series_t_supported inline auto make_p_series_t_im // Overload with a symbol set, total truncation. template -requires make_p_series_t_supported inline auto make_p_series_t_impl(const symbol_set &ss, const U &d, - const Args &...names) +requires make_p_series_t_supported +inline auto make_p_series_t_impl(const symbol_set &ss, const U &d, const Args &...names) { // Convert d to the degree type. const auto deg = ::obake::safe_cast>>(d); @@ -745,9 +750,9 @@ requires make_p_series_t_supported inline auto make_p_series_t_im if (obake_unlikely(it == ss.end() || *it != s)) { using namespace ::fmt::literals; - obake_throw(::std::invalid_argument, - "Cannot create a power series with symbol set {} from the " - "generator '{}': the generator is not in the symbol set"_format(detail::to_string(ss), s)); + obake_throw(::std::invalid_argument, fmt::format("Cannot create a power series with symbol set {} from the " + "generator '{}': the generator is not in the symbol set", + detail::to_string(ss), s)); } // Set to 1 the exponent of the corresponding generator. @@ -787,8 +792,8 @@ namespace detail template // NOTE: for partial degree truncation we can re-use the concepts // used in total degree truncation. -requires make_p_series_t_supported inline auto make_p_series_p_impl(const U &d, const symbol_set &tss, - const Args &...names) +requires make_p_series_t_supported +inline auto make_p_series_p_impl(const U &d, const symbol_set &tss, const Args &...names) { // Convert d to the degree type. const auto deg = ::obake::safe_cast>>(d); @@ -822,8 +827,8 @@ requires make_p_series_t_supported inline auto make_p_series_p_im template // NOTE: for partial degree truncation we can re-use the concepts // used in total degree truncation. -requires make_p_series_t_supported inline auto -make_p_series_p_impl(const symbol_set &ss, const U &d, const symbol_set &tss, const Args &...names) +requires make_p_series_t_supported +inline auto make_p_series_p_impl(const symbol_set &ss, const U &d, const symbol_set &tss, const Args &...names) { // Convert d to the degree type. const auto deg = ::obake::safe_cast>>(d); @@ -858,9 +863,9 @@ make_p_series_p_impl(const symbol_set &ss, const U &d, const symbol_set &tss, co if (obake_unlikely(it == ss.end() || *it != s)) { using namespace ::fmt::literals; - obake_throw(::std::invalid_argument, - "Cannot create a power series with symbol set {} from the " - "generator '{}': the generator is not in the symbol set"_format(detail::to_string(ss), s)); + obake_throw(::std::invalid_argument, fmt::format("Cannot create a power series with symbol set {} from the " + "generator '{}': the generator is not in the symbol set", + detail::to_string(ss), s)); } // Set to 1 the exponent of the corresponding generator. @@ -1014,8 +1019,8 @@ inline auto ps_addsub_impl(T &&x, U &&y) // different coefficients. // Fetch the return type. - using ret_t = decltype( - ::obake::detail::series_default_addsub_impl(::std::forward(x), ::std::forward(y))); + using ret_t = decltype(::obake::detail::series_default_addsub_impl(::std::forward(x), + ::std::forward(y))); return ::std::visit( [&x, &y](const auto &v0, const auto &v1) -> ret_t { @@ -1032,8 +1037,8 @@ inline auto ps_addsub_impl(T &&x, U &&y) using namespace ::fmt::literals; throw ::std::invalid_argument( - "Unable to {} two power series if their truncation levels do not match"_format( - AddOrSub ? "add" : "subtract")); + fmt::format("Unable to {} two power series if their truncation levels do not match", + AddOrSub ? "add" : "subtract")); } // Store the original tag. @@ -1068,8 +1073,8 @@ inline auto ps_addsub_impl(T &&x, U &&y) using namespace ::fmt::literals; throw ::std::invalid_argument( - "Unable to {} two power series if their truncation policies do not match"_format( - AddOrSub ? "add" : "subtract")); + fmt::format("Unable to {} two power series if their truncation policies do not match", + AddOrSub ? "add" : "subtract")); } }, ::obake::get_truncation(x), ::obake::get_truncation(y)); @@ -1079,7 +1084,7 @@ inline auto ps_addsub_impl(T &&x, U &&y) } // namespace detail template - requires +requires // At least one of the operands must be a power series. (any_p_series> || any_p_series>) // Check that the specialised addsub implementation @@ -1094,7 +1099,7 @@ template } template - requires(any_p_series> || any_p_series>) +requires(any_p_series> || any_p_series>) && (detail::ps_addsub_algo() != 0) inline ::obake::detail::series_default_addsub_ret_t series_sub(T &&x, U &&y) { @@ -1169,8 +1174,8 @@ inline decltype(auto) ps_in_place_addsub_impl(T &&x, U &&y) // different coefficients. // Fetch the return type. - using ret_t = decltype( - ::obake::detail::series_default_in_place_addsub_impl(::std::forward(x), ::std::forward(y))); + using ret_t = decltype(::obake::detail::series_default_in_place_addsub_impl(::std::forward(x), + ::std::forward(y))); return ::std::visit( [&x, &y](const auto &v0, const auto &v1) -> ret_t { @@ -1186,9 +1191,9 @@ inline decltype(auto) ps_in_place_addsub_impl(T &&x, U &&y) if (obake_unlikely(v0 != v1)) { using namespace ::fmt::literals; - throw ::std::invalid_argument( - "Unable to {} two power series in place if " - "their truncation levels do not match"_format(AddOrSub ? "add" : "subtract")); + throw ::std::invalid_argument(fmt::format("Unable to {} two power series in place if " + "their truncation levels do not match", + AddOrSub ? "add" : "subtract")); } // Store the original tag. @@ -1222,8 +1227,8 @@ inline decltype(auto) ps_in_place_addsub_impl(T &&x, U &&y) using namespace ::fmt::literals; throw ::std::invalid_argument( - "Unable to {} two power series in place if their truncation policies do not match"_format( - AddOrSub ? "add" : "subtract")); + fmt::format("Unable to {} two power series in place if their truncation policies do not match", + AddOrSub ? "add" : "subtract")); } }, ::obake::get_truncation(x), ::obake::get_truncation(y)); @@ -1233,7 +1238,7 @@ inline decltype(auto) ps_in_place_addsub_impl(T &&x, U &&y) } // namespace detail template - requires +requires // T must be a power series. // NOTE: if T is not a power series and U is, // then there are the following possibilities: @@ -1248,15 +1253,15 @@ template // Check that the specialised addsub implementation // for power series is avaialable and appropriate. // Otherwise, the default series addsub will be used. - && (detail::ps_in_place_addsub_algo() != 0) inline remove_cvref_t &series_in_place_add(T &&x, - U &&y) + &&(detail::ps_in_place_addsub_algo() != 0) inline remove_cvref_t &series_in_place_add(T &&x, + U &&y) { return detail::ps_in_place_addsub_impl(::std::forward(x), ::std::forward(y)); } template - requires any_p_series> && (detail::ps_in_place_addsub_algo() != 0) inline remove_cvref_t &series_in_place_sub(T &&x, U &&y) +requires any_p_series> &&(detail::ps_in_place_addsub_algo() + != 0) inline remove_cvref_t &series_in_place_sub(T &&x, U &&y) { return detail::ps_in_place_addsub_impl(::std::forward(x), ::std::forward(y)); } @@ -1380,8 +1385,9 @@ requires(detail::ps_mul_algo, p_series>() == true) inline // Exponentiation: we re-use the poly implementation, ensuring // that the output is properly truncated. template - requires any_p_series> && (customisation::internal::series_default_pow_algo != 0) inline customisation::internal::series_default_pow_ret_t pow(T &&x, U &&y) +requires any_p_series> &&( + customisation::internal::series_default_pow_algo< + T &&, U &&> != 0) inline customisation::internal::series_default_pow_ret_t pow(T &&x, U &&y) { // Store x's tag. auto orig_tag = x.tag(); @@ -1405,8 +1411,8 @@ template // even if the input object(s) have truncation (e.g., // empty x). Not sure what's the best way of dealing with this. template - requires any_p_series> && (polynomials::detail::poly_subs_algo != 0) inline polynomials::detail::poly_subs_ret_t subs(T &&x, const symbol_map &sm) +requires any_p_series> &&(polynomials::detail::poly_subs_algo != 0) inline polynomials:: + detail::poly_subs_ret_t subs(T &&x, const symbol_map &sm) { return polynomials::detail::poly_subs_impl(::std::forward(x), sm); } @@ -1420,8 +1426,8 @@ template // value is not truncated even if x is. If this becomes a problem, // we can enable this specialisation only for some values of diff_algo. template - requires any_p_series> && (polynomials::detail::poly_diff_algo != 0) inline polynomials::detail::poly_diff_ret_t diff(T &&x, const ::std::string &s) +requires any_p_series> &&(polynomials::detail::poly_diff_algo != 0) inline polynomials::detail:: + poly_diff_ret_t diff(T &&x, const ::std::string &s) { return polynomials::detail::poly_diff_impl(::std::forward(x), s); } @@ -1435,8 +1441,8 @@ template // value is not truncated even if x is. If this becomes a problem, // we can enable this specialisation only for some values of integrate_algo. template - requires any_p_series> && (polynomials::detail::poly_integrate_algo != 0) inline polynomials::detail::poly_integrate_ret_t integrate(T &&x, const ::std::string &s) +requires any_p_series> &&(polynomials::detail::poly_integrate_algo != 0) inline polynomials:: + detail::poly_integrate_ret_t integrate(T &&x, const ::std::string &s) { auto ret = polynomials::detail::poly_integrate_impl(::std::forward(x), s); diff --git a/include/obake/series.hpp b/include/obake/series.hpp index 5171fa3f..5b2b3d05 100644 --- a/include/obake/series.hpp +++ b/include/obake/series.hpp @@ -257,217 +257,215 @@ concept any_series = detail::is_series_impl::value; namespace detail { - // A bunch of scoped enums used to fine-tune at compile-time - // the behaviour of the term insertion helpers below. - // NOTE: use scoped enums instead of plain bools to avoid - // mixing up the order of the flags when invoking the helpers. - enum class sat_check_zero : bool { off, on }; - enum class sat_check_compat_key : bool { off, on }; - enum class sat_check_table_size : bool { off, on }; - enum class sat_assume_unique : bool { off, on }; - - // Helper for inserting a term into a series table. - template - inline void series_add_term_table(S & s, Table & t, T && key, Args && ...args) - { - // Determine the key/cf types. - using key_type = series_key_t<::std::remove_reference_t>; - using cf_type = series_cf_t<::std::remove_reference_t>; - static_assert(::std::is_same_v>); - - // Cache a reference to the symbol set. - [[maybe_unused]] const auto &ss = s.get_symbol_set(); - - if constexpr (CheckTableSize == sat_check_table_size::on) { - // LCOV_EXCL_START - // Check the table size, if requested. - if (obake_unlikely(t.size() == s._get_max_table_size())) { - // The table size is already the maximum allowed, don't - // attempt the insertion. - obake_throw(::std::overflow_error, "Cannot attempt the insertion of a new term into a series: the " - "destination table already contains the maximum number of terms (" - + detail::to_string(s._get_max_table_size()) + ")"); - } - // LCOV_EXCL_STOP +// A bunch of scoped enums used to fine-tune at compile-time +// the behaviour of the term insertion helpers below. +// NOTE: use scoped enums instead of plain bools to avoid +// mixing up the order of the flags when invoking the helpers. +enum class sat_check_zero : bool { off, on }; +enum class sat_check_compat_key : bool { off, on }; +enum class sat_check_table_size : bool { off, on }; +enum class sat_assume_unique : bool { off, on }; + +// Helper for inserting a term into a series table. +template +inline void series_add_term_table(S &s, Table &t, T &&key, Args &&...args) +{ + // Determine the key/cf types. + using key_type = series_key_t<::std::remove_reference_t>; + using cf_type = series_cf_t<::std::remove_reference_t>; + static_assert(::std::is_same_v>); + + // Cache a reference to the symbol set. + [[maybe_unused]] const auto &ss = s.get_symbol_set(); + + if constexpr (CheckTableSize == sat_check_table_size::on) { + // LCOV_EXCL_START + // Check the table size, if requested. + if (obake_unlikely(t.size() == s._get_max_table_size())) { + // The table size is already the maximum allowed, don't + // attempt the insertion. + obake_throw(::std::overflow_error, "Cannot attempt the insertion of a new term into a series: the " + "destination table already contains the maximum number of terms (" + + detail::to_string(s._get_max_table_size()) + ")"); } + // LCOV_EXCL_STOP + } - if constexpr (CheckCompatKey == sat_check_compat_key::on) { - // Check key for compatibility, if requested. - if (obake_unlikely(!::obake::key_is_compatible(::std::as_const(key), ss))) { - using namespace ::fmt::literals; - - // The key is not compatible with the symbol set. - if constexpr (is_stream_insertable_v) { - // A slightly better error message if we can - // produce a string representation of the key. - obake_throw(::std::invalid_argument, "Cannot add a term to a series: the term's key, '{}', " - "is not compatible with the series' symbol set, {}"_format( - ::std::as_const(key), detail::to_string(ss))); - } else { - obake_throw(::std::invalid_argument, - "Cannot add a term to a series: the term's key is not " - "compatible with the series' symbol set, {}"_format(detail::to_string(ss))); - } + if constexpr (CheckCompatKey == sat_check_compat_key::on) { + // Check key for compatibility, if requested. + if (obake_unlikely(!::obake::key_is_compatible(::std::as_const(key), ss))) { + using namespace ::fmt::literals; + + // The key is not compatible with the symbol set. + if constexpr (is_stream_insertable_v) { + // A slightly better error message if we can + // produce a string representation of the key. + obake_throw(::std::invalid_argument, fmt::format("Cannot add a term to a series: the term's key, '{}', " + "is not compatible with the series' symbol set, {}", + ::std::as_const(key), detail::to_string(ss))); + } else { + obake_throw(::std::invalid_argument, fmt::format("Cannot add a term to a series: the term's key is not " + "compatible with the series' symbol set, {}", + detail::to_string(ss))); } - } else { - // Otherwise, assert that the key is compatible. - // There are no situations so far in which we may - // want to allow adding an incompatible key. - assert(::obake::key_is_compatible(::std::as_const(key), ss)); } + } else { + // Otherwise, assert that the key is compatible. + // There are no situations so far in which we may + // want to allow adding an incompatible key. + assert(::obake::key_is_compatible(::std::as_const(key), ss)); + } - // Attempt the insertion. - const auto res = t.try_emplace(detail::fcast(::std::forward(key)), ::std::forward(args)...); + // Attempt the insertion. + const auto res = t.try_emplace(detail::fcast(::std::forward(key)), ::std::forward(args)...); - if constexpr (AssumeUnique == sat_assume_unique::on) { - // Assert that we actually performed an insertion, - // in case we are assuming the term is unique. - assert(res.second); - } + if constexpr (AssumeUnique == sat_assume_unique::on) { + // Assert that we actually performed an insertion, + // in case we are assuming the term is unique. + assert(res.second); + } - try { - if (AssumeUnique == sat_assume_unique::on || res.second) { - // The insertion took place. Change - // the sign of the newly-inserted term, - // in case of negative insertion. - if constexpr (!Sign) { - ::obake::negate(res.first->second); + try { + if (AssumeUnique == sat_assume_unique::on || res.second) { + // The insertion took place. Change + // the sign of the newly-inserted term, + // in case of negative insertion. + if constexpr (!Sign) { + ::obake::negate(res.first->second); + } + } else { + // The insertion did not take place because a term with + // the same key exists already. Add/sub the input coefficient + // to/from the existing one. + + // Determine if we are inserting a coefficient, or + // a pack that can be used to construct a coefficient. + constexpr auto args_is_cf = []() { + if constexpr (sizeof...(args) == 1u) { + return ::std::is_same_v...>; + } else { + return false; } - } else { - // The insertion did not take place because a term with - // the same key exists already. Add/sub the input coefficient - // to/from the existing one. - - // Determine if we are inserting a coefficient, or - // a pack that can be used to construct a coefficient. - constexpr auto args_is_cf = []() { - if constexpr (sizeof...(args) == 1u) { - return ::std::is_same_v...>; - } else { - return false; - } - }(); + }(); - if constexpr (Sign) { - if constexpr (args_is_cf) { - // NOTE: if we are inserting a coefficient, use it directly. - res.first->second += detail::fcast(::std::forward(args)...); - } else { - // Otherwise, construct a coefficient from the input pack - // and add that instead. - res.first->second += cf_type(::std::forward(args)...); - } + if constexpr (Sign) { + if constexpr (args_is_cf) { + // NOTE: if we are inserting a coefficient, use it directly. + res.first->second += detail::fcast(::std::forward(args)...); } else { - if constexpr (args_is_cf) { - res.first->second -= detail::fcast(::std::forward(args)...); - } else { - res.first->second -= cf_type(::std::forward(args)...); - } + // Otherwise, construct a coefficient from the input pack + // and add that instead. + res.first->second += cf_type(::std::forward(args)...); } - } - - if constexpr (CheckZero == sat_check_zero::on) { - // If requested, check whether the term we inserted - // or modified is zero. If it is, erase it. - if (obake_unlikely(::obake::key_is_zero(res.first->first, ss) - || ::obake::is_zero(::std::as_const(res.first->second)))) { - t.erase(res.first); + } else { + if constexpr (args_is_cf) { + res.first->second -= detail::fcast(::std::forward(args)...); + } else { + res.first->second -= cf_type(::std::forward(args)...); } } - } catch (...) { - // NOTE: if something threw, the table might now be in an - // inconsistent state. Clear it out before rethrowing. - t.clear(); - - throw; } - } - // Helper for inserting a term into a series. - template - inline void series_add_term(S & s, T && key, Args && ...args) - { - // Determine the key type. - using key_type = series_key_t<::std::remove_reference_t>; - static_assert(::std::is_same_v>); + if constexpr (CheckZero == sat_check_zero::on) { + // If requested, check whether the term we inserted + // or modified is zero. If it is, erase it. + if (obake_unlikely(::obake::key_is_zero(res.first->first, ss) + || ::obake::is_zero(::std::as_const(res.first->second)))) { + t.erase(res.first); + } + } + } catch (...) { + // NOTE: if something threw, the table might now be in an + // inconsistent state. Clear it out before rethrowing. + t.clear(); - auto &s_table = s._get_s_table(); - const auto s_table_size = s_table.size(); - assert(s_table_size > 0u); + throw; + } +} - if (s_table_size == 1u) { - // NOTE: forcibly set the table size check to off (for a single - // table, the size limit is always the full range of size_type). - detail::series_add_term_table( - s, s_table[0], ::std::forward(key), ::std::forward(args)...); - } else { - // Compute the hash of the key via obake::hash(). - const auto k_hash = ::obake::hash(::std::as_const(key)); +// Helper for inserting a term into a series. +template +inline void series_add_term(S &s, T &&key, Args &&...args) +{ + // Determine the key type. + using key_type = series_key_t<::std::remove_reference_t>; + static_assert(::std::is_same_v>); + + auto &s_table = s._get_s_table(); + const auto s_table_size = s_table.size(); + assert(s_table_size > 0u); + + if (s_table_size == 1u) { + // NOTE: forcibly set the table size check to off (for a single + // table, the size limit is always the full range of size_type). + detail::series_add_term_table( + s, s_table[0], ::std::forward(key), ::std::forward(args)...); + } else { + // Compute the hash of the key via obake::hash(). + const auto k_hash = ::obake::hash(::std::as_const(key)); - // Determine the destination table. - const auto table_idx = static_cast(k_hash & (s_table_size - 1u)); + // Determine the destination table. + const auto table_idx = static_cast(k_hash & (s_table_size - 1u)); - // Proceed to the insertion. - detail::series_add_term_table( - s, s_table[table_idx], ::std::forward(key), ::std::forward(args)...); - } + // Proceed to the insertion. + detail::series_add_term_table( + s, s_table[table_idx], ::std::forward(key), ::std::forward(args)...); } +} - // Machinery for series' generic constructor. - template - constexpr int series_generic_ctor_algorithm_impl() - { - // NOTE: check first if series is a well-formed - // type (that is, K and C satisfy the key/cf requirements). - // Like this, if this function is instantiated with bogus - // types, it will return 0 rather than giving a hard error. - if constexpr (is_detected_v) { - using series_t = series; - using rT = remove_cvref_t; - - if constexpr (::std::is_same_v) { - // Avoid competition with the copy/move ctors. - return 0; - } else if constexpr (series_rank < series_rank) { - // Construction from lesser rank requires - // to be able to construct C from T. - return ::std::is_constructible_v ? 1 : 0; - } else if constexpr (series_rank == series_rank) { - if constexpr (::std::is_same_v, K>) { - // Construction from equal rank and same key (but at least - // one of cf and tag must differ, otherwise we are in a - // copy/move situation). Requires - // to be able to construct C from the coefficient type of T. - // The construction argument will be a const reference or an rvalue - // reference, depending on whether T && is a mutable rvalue reference or not. - // NOTE: we need to explicitly put T && here because this function - // is invoked with a type T which was deduced from a forwarding - // reference (T &&). - using cf_conv_t = ::std::conditional_t, series_cf_t &&, - const series_cf_t &>; - return ::std::is_constructible_v ? 2 : 0; - } else { - return 0; - } +// Machinery for series' generic constructor. +template +constexpr int series_generic_ctor_algorithm_impl() +{ + // NOTE: check first if series is a well-formed + // type (that is, K and C satisfy the key/cf requirements). + // Like this, if this function is instantiated with bogus + // types, it will return 0 rather than giving a hard error. + if constexpr (is_detected_v) { + using series_t = series; + using rT = remove_cvref_t; + + if constexpr (::std::is_same_v) { + // Avoid competition with the copy/move ctors. + return 0; + } else if constexpr (series_rank < series_rank) { + // Construction from lesser rank requires + // to be able to construct C from T. + return ::std::is_constructible_v ? 1 : 0; + } else if constexpr (series_rank == series_rank) { + if constexpr (::std::is_same_v, K>) { + // Construction from equal rank and same key (but at least + // one of cf and tag must differ, otherwise we are in a + // copy/move situation). Requires + // to be able to construct C from the coefficient type of T. + // The construction argument will be a const reference or an rvalue + // reference, depending on whether T && is a mutable rvalue reference or not. + // NOTE: we need to explicitly put T && here because this function + // is invoked with a type T which was deduced from a forwarding + // reference (T &&). + using cf_conv_t = ::std::conditional_t, series_cf_t &&, + const series_cf_t &>; + return ::std::is_constructible_v ? 2 : 0; } else { - // Construction from higher rank. Requires that - // series_t can be constructed from the coefficient - // type of T. - using series_conv_t = ::std::conditional_t, series_cf_t &&, - const series_cf_t &>; - return ::std::is_constructible_v ? 3 : 0; + return 0; } } else { - return 0; + // Construction from higher rank. Requires that + // series_t can be constructed from the coefficient + // type of T. + using series_conv_t = ::std::conditional_t, series_cf_t &&, + const series_cf_t &>; + return ::std::is_constructible_v ? 3 : 0; } + } else { + return 0; } +} - template - inline constexpr int series_generic_ctor_algorithm = detail::series_generic_ctor_algorithm_impl(); +template +inline constexpr int series_generic_ctor_algorithm = detail::series_generic_ctor_algorithm_impl(); } // namespace detail @@ -495,173 +493,173 @@ concept SeriesConvertible = is_series_convertible_v; namespace detail { - // A small hashing wrapper for keys. It accomplishes two tasks: - // - force the evaluation of a key through const reference, - // so that, in the Key requirements, we can request hashability - // through const lvalue ref; - // - provide additional mixing. - struct series_key_hasher { - // NOTE: here we are duplicating a bit of internal - // abseil code for integral hash mixing, with the intent - // of avoiding the per-process seeding that abseil does. - // See here for the original code: - // https://github.com/abseil/abseil-cpp/blob/37dd2562ec830d547a1524bb306be313ac3f2556/absl/hash/internal/hash.h#L754 - // If/when abseil starts supporting DLL builds, we can - // remove this code and switch back to using abseil's - // own hash machinery for mixing. - static constexpr ::std::uint64_t kMul - = sizeof(::std::size_t) == 4u ? ::std::uint64_t{0xcc9e2d51ull} : ::std::uint64_t{0x9ddfea08eb382d69ull}; - ABSL_ATTRIBUTE_ALWAYS_INLINE static ::std::uint64_t Mix(::std::uint64_t state, ::std::uint64_t v) - { - using MultType = ::std::conditional_t; - // We do the addition in 64-bit space to make sure the 128-bit - // multiplication is fast. If we were to do it as MultType the compiler has - // to assume that the high word is non-zero and needs to perform 2 - // multiplications instead of one. - MultType m = state + v; - m *= kMul; - return static_cast<::std::uint64_t>(m ^ (m >> (sizeof(m) * 8 / 2))); - } - template - ::std::size_t operator()(const K &k) const noexcept(noexcept(::obake::hash(k))) - { - // NOTE: mix with a compile-time seed. - return static_cast<::std::size_t>( - series_key_hasher::Mix(15124392053943080205ull, static_cast<::std::uint64_t>(::obake::hash(k)))); - } - }; - - // Wrapper to force key comparison via const lvalue refs. - struct series_key_comparer { - template - constexpr bool operator()(const K &k1, const K &k2) const noexcept(noexcept(k1 == k2)) - { - return k1 == k2; - } - }; +// A small hashing wrapper for keys. It accomplishes two tasks: +// - force the evaluation of a key through const reference, +// so that, in the Key requirements, we can request hashability +// through const lvalue ref; +// - provide additional mixing. +struct series_key_hasher { + // NOTE: here we are duplicating a bit of internal + // abseil code for integral hash mixing, with the intent + // of avoiding the per-process seeding that abseil does. + // See here for the original code: + // https://github.com/abseil/abseil-cpp/blob/37dd2562ec830d547a1524bb306be313ac3f2556/absl/hash/internal/hash.h#L754 + // If/when abseil starts supporting DLL builds, we can + // remove this code and switch back to using abseil's + // own hash machinery for mixing. + static constexpr ::std::uint64_t kMul + = sizeof(::std::size_t) == 4u ? ::std::uint64_t{0xcc9e2d51ull} : ::std::uint64_t{0x9ddfea08eb382d69ull}; + ABSL_ATTRIBUTE_ALWAYS_INLINE static ::std::uint64_t Mix(::std::uint64_t state, ::std::uint64_t v) + { + using MultType = ::std::conditional_t; + // We do the addition in 64-bit space to make sure the 128-bit + // multiplication is fast. If we were to do it as MultType the compiler has + // to assume that the high word is non-zero and needs to perform 2 + // multiplications instead of one. + MultType m = state + v; + m *= kMul; + return static_cast<::std::uint64_t>(m ^ (m >> (sizeof(m) * 8 / 2))); + } + template + ::std::size_t operator()(const K &k) const noexcept(noexcept(::obake::hash(k))) + { + // NOTE: mix with a compile-time seed. + return static_cast<::std::size_t>( + series_key_hasher::Mix(15124392053943080205ull, static_cast<::std::uint64_t>(::obake::hash(k)))); + } +}; - // Small helper to clear() a nonconst - // rvalue reference to a series. This is used in various places - // where we might end up moving away individual coefficients from an input series, - // which may leave the series in an inconsistent state. With this - // RAII struct, we'll ensure the series is cleared out before - // leaving the scope (either as part of regular program - // flow or in case of exception). - // NOTE: this is different from the clear() that is called - // in debug mode during move operations: in that situation, - // we are guaranteeing that after the move the series - // is destructible and assignable, so the state of the series - // does not matter as long as we can revive it. This clearer, - // on the other hand, is not called during move operations, - // but only when it might make sense, for optimisation purposes, - // to move individual coefficients - hence the move semantics - // guarantee does not apply. - template - struct series_rref_clearer { - series_rref_clearer(T &&ref) : m_ref(::std::forward(ref)) {} - ~series_rref_clearer() - { - if constexpr (is_mutable_rvalue_reference_v) { - m_ref.clear(); - } - } - T &&m_ref; - }; +// Wrapper to force key comparison via const lvalue refs. +struct series_key_comparer { + template + constexpr bool operator()(const K &k1, const K &k2) const noexcept(noexcept(k1 == k2)) + { + return k1 == k2; + } +}; - // Helper to extend the keys of "from" with the symbol insertion map ins_map. - // The new series will be written to "to". The coefficient type of "to" - // may be different from the coefficient type of "from", in which case a coefficient - // conversion will take place. "to" is supposed to have the correct symbol set already, - // but, apart from that, it must be empty, and the number of segments and space - // reservation will be taken from "from". - // Another precondition is that to and from must be distinct objects. - template - inline void series_sym_extender(To & to, From && from, const symbol_idx_map &ins_map) +// Small helper to clear() a nonconst +// rvalue reference to a series. This is used in various places +// where we might end up moving away individual coefficients from an input series, +// which may leave the series in an inconsistent state. With this +// RAII struct, we'll ensure the series is cleared out before +// leaving the scope (either as part of regular program +// flow or in case of exception). +// NOTE: this is different from the clear() that is called +// in debug mode during move operations: in that situation, +// we are guaranteeing that after the move the series +// is destructible and assignable, so the state of the series +// does not matter as long as we can revive it. This clearer, +// on the other hand, is not called during move operations, +// but only when it might make sense, for optimisation purposes, +// to move individual coefficients - hence the move semantics +// guarantee does not apply. +template +struct series_rref_clearer { + series_rref_clearer(T &&ref) : m_ref(::std::forward(ref)) {} + ~series_rref_clearer() { - // NOTE: we assume that this helper is - // invoked with a non-empty insertion map, and an empty - // "to" series. "to" must have the correct symbol set. - assert(!ins_map.empty()); - assert(to.empty()); - if constexpr (::std::is_same_v, remove_cvref_t>) { - assert(&to != &from); + if constexpr (is_mutable_rvalue_reference_v) { + m_ref.clear(); } + } + T &&m_ref; +}; - // Ensure that the key type of From - // is symbol mergeable (via const lvalue ref). - static_assert(is_symbols_mergeable_key_v> &>); - - // We may end up moving coefficients from "from" in the conversion to "to". - // Make sure we will clear "from" out properly. - series_rref_clearer from_c(::std::forward(from)); - - // Cache the original symbol set. - const auto &orig_ss = from.get_symbol_set(); - - // Set the number of segments, reserve space. - const auto from_log2_size = from.get_s_size(); - to.set_n_segments(from_log2_size); - to.reserve(::obake::safe_cast(from.size())); - - // Establish if we need to check for zero coefficients - // when inserting. We don't if the coefficient types of to and from - // coincide (i.e., no cf conversion takes place), - // otherwise the conversion might generate zeroes. - constexpr auto check_zero - = static_cast(::std::is_same_v, series_cf_t>>); - - // Merge the terms, distinguishing the segmented vs non-segmented case. - if (from_log2_size) { - for (auto &t : from._get_s_table()) { - for (auto &term : t) { - // NOTE: old clang does not like structured - // bindings in the for loop. - auto &k = term.first; - auto &c = term.second; +// Helper to extend the keys of "from" with the symbol insertion map ins_map. +// The new series will be written to "to". The coefficient type of "to" +// may be different from the coefficient type of "from", in which case a coefficient +// conversion will take place. "to" is supposed to have the correct symbol set already, +// but, apart from that, it must be empty, and the number of segments and space +// reservation will be taken from "from". +// Another precondition is that to and from must be distinct objects. +template +inline void series_sym_extender(To &to, From &&from, const symbol_idx_map &ins_map) +{ + // NOTE: we assume that this helper is + // invoked with a non-empty insertion map, and an empty + // "to" series. "to" must have the correct symbol set. + assert(!ins_map.empty()); + assert(to.empty()); + if constexpr (::std::is_same_v, remove_cvref_t>) { + assert(&to != &from); + } - // Compute the merged key. - auto merged_key = ::obake::key_merge_symbols(k, ins_map, orig_ss); - - // Insert the term. We need the following checks: - // - zero check, in case the coefficient type changes, - // - table size check, because even if we know the - // max table size was not exceeded in the original series, - // it might be now (as the merged key may end up in a different - // table). - // NOTE: in the runtime requirements for key_merge_symbol(), we impose - // that symbol merging does not affect is_zero(), compatibility and - // uniqueness. - if constexpr (is_mutable_rvalue_reference_v) { - detail::series_add_term(to, ::std::move(merged_key), ::std::move(c)); - } else { - detail::series_add_term(to, ::std::move(merged_key), ::std::as_const(c)); - } - } - } - } else { - auto &to_table = to._get_s_table()[0]; + // Ensure that the key type of From + // is symbol mergeable (via const lvalue ref). + static_assert(is_symbols_mergeable_key_v> &>); + + // We may end up moving coefficients from "from" in the conversion to "to". + // Make sure we will clear "from" out properly. + series_rref_clearer from_c(::std::forward(from)); + + // Cache the original symbol set. + const auto &orig_ss = from.get_symbol_set(); + + // Set the number of segments, reserve space. + const auto from_log2_size = from.get_s_size(); + to.set_n_segments(from_log2_size); + to.reserve(::obake::safe_cast(from.size())); + + // Establish if we need to check for zero coefficients + // when inserting. We don't if the coefficient types of to and from + // coincide (i.e., no cf conversion takes place), + // otherwise the conversion might generate zeroes. + constexpr auto check_zero + = static_cast(::std::is_same_v, series_cf_t>>); + + // Merge the terms, distinguishing the segmented vs non-segmented case. + if (from_log2_size) { + for (auto &t : from._get_s_table()) { + for (auto &term : t) { + // NOTE: old clang does not like structured + // bindings in the for loop. + auto &k = term.first; + auto &c = term.second; - for (const auto &[k, c] : from._get_s_table()[0]) { // Compute the merged key. auto merged_key = ::obake::key_merge_symbols(k, ins_map, orig_ss); - // Insert the term: the only check we may need is check_zero, in case - // the coefficient type changes. We know that the table size cannot be - // exceeded as we are dealing with a single table. + // Insert the term. We need the following checks: + // - zero check, in case the coefficient type changes, + // - table size check, because even if we know the + // max table size was not exceeded in the original series, + // it might be now (as the merged key may end up in a different + // table). + // NOTE: in the runtime requirements for key_merge_symbol(), we impose + // that symbol merging does not affect is_zero(), compatibility and + // uniqueness. if constexpr (is_mutable_rvalue_reference_v) { - detail::series_add_term_table( - to, to_table, ::std::move(merged_key), ::std::move(c)); + detail::series_add_term(to, ::std::move(merged_key), ::std::move(c)); } else { - detail::series_add_term_table( - to, to_table, ::std::move(merged_key), ::std::as_const(c)); + detail::series_add_term(to, ::std::move(merged_key), ::std::as_const(c)); } } } + } else { + auto &to_table = to._get_s_table()[0]; + + for (const auto &[k, c] : from._get_s_table()[0]) { + // Compute the merged key. + auto merged_key = ::obake::key_merge_symbols(k, ins_map, orig_ss); + + // Insert the term: the only check we may need is check_zero, in case + // the coefficient type changes. We know that the table size cannot be + // exceeded as we are dealing with a single table. + if constexpr (is_mutable_rvalue_reference_v) { + detail::series_add_term_table(to, to_table, ::std::move(merged_key), + ::std::move(c)); + } else { + detail::series_add_term_table(to, to_table, ::std::move(merged_key), + ::std::as_const(c)); + } + } } +} } // namespace detail @@ -1857,9 +1855,11 @@ requires(series_default_pow_algo != 0) inline series_default_pow_ret if (obake_unlikely(!::obake::safe_convert(un, e))) { if constexpr (is_stream_insertable_v) { // Provide better error message if U is ostreamable. - obake_throw(::std::invalid_argument, "Invalid exponent for series exponentiation via repeated " - "multiplications: the exponent ({}) cannot be converted into " - "a non-negative integral value"_format(e)); + obake_throw(::std::invalid_argument, + fmt::format("Invalid exponent for series exponentiation via repeated " + "multiplications: the exponent ({}) cannot be converted into " + "a non-negative integral value", + e)); } else { obake_throw(::std::invalid_argument, "Invalid exponent for series exponentiation via repeated " @@ -1869,12 +1869,13 @@ requires(series_default_pow_algo != 0) inline series_default_pow_ret return internal::series_pow_from_cache(b, un); } else { - obake_throw( - ::std::invalid_argument, - "Cannot compute the power of a series of type '{}': the series does not consist of a single coefficient, " - "and exponentiation via repeated multiplications is not possible (either because the " - "exponent cannot be converted to a non-negative integral value, or because the " - "series/coefficient types do not support the necessary operations)"_format(::obake::type_name())); + obake_throw(::std::invalid_argument, + fmt::format("Cannot compute the power of a series of type '{}': the series does not consist of a " + "single coefficient, " + "and exponentiation via repeated multiplications is not possible (either because the " + "exponent cannot be converted to a non-negative integral value, or because the " + "series/coefficient types do not support the necessary operations)", + ::obake::type_name())); } } diff --git a/src/polynomials/packed_monomial.cpp b/src/polynomials/packed_monomial.cpp index 06536a9c..f0927831 100644 --- a/src/polynomials/packed_monomial.cpp +++ b/src/polynomials/packed_monomial.cpp @@ -74,7 +74,7 @@ void packed_monomial_stream_insert(::std::ostream &os, const packed_monomial // The exponent is not unitary, // print it. using namespace ::fmt::literals; - os << "**{}"_format(tmp); + os << fmt::format("**{}", tmp); } } } @@ -169,11 +169,11 @@ void packed_monomial_tex_stream_insert(::std::ostream &os, const packed_monomial } // Print the symbol name. - *cur_oss << "{{{}}}"_format(var); + *cur_oss << fmt::format("{{{}}}", var); // Raise to power, if the exponent is not one. if (!tmp_mp.is_one()) { - *cur_oss << "^{{{}}}"_format(tmp_mp); + *cur_oss << fmt::format("^{{{}}}", tmp_mp); } } } @@ -183,13 +183,13 @@ void packed_monomial_tex_stream_insert(::std::ostream &os, const packed_monomial if (!num_str.empty() && !den_str.empty()) { // We have both negative and positive exponents, // print them both in a fraction. - os << "\\frac{{{}}}{{{}}}"_format(num_str, den_str); + os << fmt::format("\\frac{{{}}}{{{}}}", num_str, den_str); } else if (!num_str.empty() && den_str.empty()) { // Only positive exponents. os << num_str; } else if (num_str.empty() && !den_str.empty()) { // Only negative exponents, display them as 1/something. - os << "\\frac{{1}}{{{}}}"_format(den_str); + os << fmt::format("\\frac{{1}}{{{}}}", den_str); } else { // We did not write anything to the stream. // It means that all variables have zero @@ -685,11 +685,11 @@ ::std::pair> packed_monomial_monomial_integrate(const pack // we are not integrating x**-1. if (obake_unlikely(tmp == T(-1))) { using namespace ::fmt::literals; - obake_throw( - ::std::domain_error, - "Cannot integrate a packed monomial: the exponent of the integration variable ('{}') is -1, " - "and the integration would generate a logarithmic term"_format( - *ss.nth(static_cast(i)))); + obake_throw(::std::domain_error, + fmt::format("Cannot integrate a packed monomial: the exponent of the integration " + "variable ('{}') is -1, " + "and the integration would generate a logarithmic term", + *ss.nth(static_cast(i)))); } }