/* ***************************************************************************** * kblib is a general utility library for C++14 and C++17, intended to provide * performant high-level abstractions and more expressive ways to do simple * things. * * Copyright (c) 2021 killerbee * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ****************************************************************************/ /** * @file * @brief Provides by-value algorithms which produce containers. * * @author killerbee * @date 2019-2021 * @copyright GNU General Public Licence v3.0 */ #ifndef KBLIB_BUILD_H #define KBLIB_BUILD_H #include "tdecl.h" #include "algorithm.h" #include "fakestd.h" #include "iterators.h" #include "traits.h" #include #include #include #include #if __cplusplus >= 201703L # include #endif namespace KBLIB_NS { /** * @brief Constructs a container by applying a UnaryFunction to every element of * an input range * * @param first Beginning of input range. * @param last End of input range. * @param f A UnaryFunction to apply to each element of the input range. * @tparam Container The type of container to return. Must be an AllocatorAware * SequenceContainer. * @param allocator The allocator to use for the returned container. * @return A Container where each element is a transformed element of the input * range. */ template KBLIB_NODISCARD auto build(InputIt first, InputIt last, UnaryFunction f, typename Container::allocator_type allocator = typename Container::allocator_type{}) -> Container { Container out(allocator); std::transform(first, last, std::back_inserter(out), f); return static_cast(out.resize(out.size())), out; } /** * @brief Constructs a container by applying a BinaryFunction to every pair of * elements in the input ranges * @pre The size of the second input range must be equal to or less than the * size of the first. * * @param first Beginning of input range. * @param last End of input range. * @param first2 Beginning of second input range * @param f A BinaryFunction to apply to the elements of the two input ranges. * @tparam Container The type of container to return. Must be an AllocatorAware * SequenceContainer. * @param allocator The allocator to use for the returned container. * @return A Container where each element is generated from a corresponding pair * of elements in the input ranges. */ template KBLIB_NODISCARD auto build(InputIt first, InputIt last, InputIt2 first2, BinaryFunction f, typename Container::allocator_type allocator = typename Container::allocator_type{}) -> Container { Container out(allocator); std::transform(first, last, first2, std::back_inserter(out), f); return out; } /** * @brief Constructs an array-like container by applying a UnaryFunction to * every element of an input range * @pre The size of the input range must be equal to or less than the size of * Container. * @remark Because Array is non-resizable, the entire Array will be * default-constructed and then the elements assigned to, rather than * copy-constructed. * * @param first Beginning of input range. * @param last End of input range. * @param f A UnaryFunction to apply to each element of the input range. * @tparam Array The type of container to return. Must be a non-resizable * Container similar to std::array. * @return An Array where each element is a transformed element of the input * range. */ template , int> = 0> KBLIB_NODISCARD auto build(InputIt first, InputIt last, UnaryFunction f) -> Array { Array out; std::transform(first, last, out.begin(), f); return out; } /** * @brief Constructs an array-like container by applying a BinaryFunction to * every pair of elements in the input ranges * @pre The size of the second input range must be equal to or less than the * size of the first. * @remark Because Array is non-resizable, the entire Array will be * default-constructed and then the elements assigned to, rather than * copy-constructed. * * @param first Beginning of input range. * @param last End of input range. * @param first2 Beginning of second input range * @param f A BinaryFunction to apply to the elements of the two input ranges. * @tparam Array The type of container to return. Must be a non-resizable * Container similar to std::array. * @return An Array where each element is generated from a corresponding pair * of elements in the input ranges. */ template , int> = 0> KBLIB_NODISCARD auto build(InputIt first, InputIt last, InputIt2 first2, BinaryFunction f) -> Array { Array out; std::transform(first, last, first2, out.begin(), f); return out; } /** * @brief Constructs a container with elements initialized by repeatedly calling * a generating function * * @tparam Container The type of container to return. Must be an AllocatorAware * SequenceContainer. * @param f The functor to repeatedly invoke. * @param size The number of times to invoke `f`. * @param allocator The allocator to use for the returned container. * @return A Container where each element is the result of invoking `f` in * sequence. */ template KBLIB_NODISCARD auto build(Functor f, size_t size, typename Container::allocator_type allocator = typename Container::allocator_type{}) -> Container { Container out(allocator); try_reserve(out, size); std::generate_n(std::back_inserter(out), size, f); return out; } /** * @brief Constructs an array-like container with elements initialized by * repeatedly calling a generating function * @remark Because Array is non-resizable, the entire Array will be * default-constructed and then the elements assigned to, rather than * copy-constructed. * * @tparam Array The type of container to return. Must be a non-resizable * Container similar to std::array. * @param f The functor to repeatedly invoke. * @param size The number of times to invoke `f`. Defaults to the size of the * Container, which is usually correct. * @return An Array where each element is the result of invoking `f` in * sequence. */ template , int> = 0> KBLIB_NODISCARD auto build(Functor f, size_t size = std::tuple_size::value) -> Array { Array out; std::generate_n(out.begin(), size, f); return out; } // build_dy: workaround for non-allocator-aware dynamic containers /** * @brief Constructs a container by applying a UnaryFunction to every element of * an input range. Exactly like `build`, but for resizable non-AllocatorAware * Containers (which are hard to detect automatically). * * @param first Beginning of input range. * @param last End of input range. * @param f A UnaryFunction to apply to each element of the input range. * @tparam Container The type of container to return. Must be a resizable * SequenceContainer. * @return A Container where each element is a transformed element of the input * range. */ template KBLIB_NODISCARD auto build_dy(InputIt first, InputIt last, UnaryFunction f) -> Container { Container out; std::transform(first, last, std::back_inserter(out), f); return out; } /** * @brief Constructs a container by applying a BinaryFunction to every pair of * elements in the input ranges. Exactly like `build`, but for resizable * non-AllocatorAware Containers (which are hard to detect automatically). * @pre The size of the second input range must be equal to or less than the * size of the first. * * @param first Beginning of input range. * @param last End of input range. * @param first2 Beginning of second input range * @param f A BinaryFunction to apply to the elements of the two input ranges. * @tparam Container The type of container to return. Must be a resizable * SequenceContainer. * @return A Container where each element is generated from a corresponding pair * of elements in the input ranges. */ template KBLIB_NODISCARD auto build_dy(InputIt first, InputIt last, InputIt2 first2, BinaryFunction f) -> Container { Container out; std::transform(first, last, first2, std::back_inserter(out), f); return out; } /** * @brief Constructs a container with elements initialized by repeatedly calling * a generating function. Exactly like `build`, but for resizable * non-AllocatorAware Containers (which are hard to detect automatically). * * @param f The functor to repeatedly invoke. * @param size The number of times to invoke `f`. * @return A Container where each element is the result of invoking `f` in * sequence. */ template KBLIB_NODISCARD auto build_dy(Functor f, size_t size) -> Container { Container out; try_reserve(out, size); std::generate_n(std::back_inserter(out), size, f); return out; } /** * @brief * * @param r * @param allocator * @return Container */ template , int> = 0> KBLIB_NODISCARD auto build_dy(Range&& r, UnaryFunction f) -> Container { using std::begin; using std::end; Container out(kblib::size(r)); std::transform(begin(r), end(r), begin(out), std::ref(f)); return out; } #if 0 // I can't overload on both array vs. dynamic container and execution policy // in any sane way without concepts, so this whole set of functions is cut // because they're less useful than the array overloads. template KBLIB_NODISCARD auto build(ExecutionPolicy&& policy, InputIt first, InputIt last, UnaryFunction f, KBLIB_UNUSED typename Container::allocator_type = typename Container::allocator_type{}) -> Container { Container out; std::transform(policy, first, last, std::back_inserter(out), f); return static_cast(out.resize(out.size())), out; } template KBLIB_NODISCARD auto build(ExecutionPolicy&& policy, InputIt first, InputIt last, InputIt2 first2, BinaryFunction f, KBLIB_UNUSED typename Container::allocator_type = typename Container::allocator_type{}) -> Container { Container out; std::transform(policy, first, last, first2, std::back_inserter(out), f); return out; } template ::value_type, size_t>::value, int>::type = 0> KBLIB_NODISCARD auto build(ExecutionPolicy&& policy, InputIt first, InputIt last, UnaryFunction f) -> Array { Array out; std::transform(policy, first, last, out.begin(), f); return out; } template ::value_type, size_t>::value, int>::type = 0> KBLIB_NODISCARD auto build(ExecutionPolicy&& policy, InputIt first, InputIt last, InputIt2 first2, BinaryFunction f) -> Array { Array out; std::transform(policy, first, last, first2, out.begin(), f); return out; } template KBLIB_NODISCARD auto build(ExecutionPolicy&& policy, Functor f, size_t size, [[gnu::unused]] typename Container::allocator_type = typename Container::allocator_type{}) -> Container { Container out(size); std::generate_n(policy, out.begin(), size, f); return out; } template ::value_type, size_t>::value, int>::type = 0> KBLIB_NODISCARD auto build(ExecutionPolicy&& policy, Functor f, size_t size = std::tuple_size::value) -> Array { Array out; std::generate_n(policy, out.begin(), size, f); return out; } #endif namespace detail { template struct buildiota_impl { template constexpr static auto impl(std::size_t count, T value) -> Container { Container out; try_reserve(out, count); while (count-- > 0) { out.push_back(value); ++value; } return out; } template constexpr static auto impl(std::size_t count, T value, I incr) -> Container { Container out; try_reserve(out, count); while (count-- > 0) { out.push_back(value); value += incr; } return out; } }; template struct buildiota_impl { template constexpr static auto impl(T value) -> Array { Array out{}; for (auto& v : out) { v = value; ++value; } return out; } template constexpr static auto impl(T value, I incr) -> Array { Array out{}; for (auto& v : out) { v = value; value += incr; } return out; } }; } // namespace detail /** * @brief Builds a container of increasing values. * * @tparam Container Either a resizable (like std::vector) or non-resizable * (like std::array) Container. * @remark If Container is non-resizable, then elements are first * value-initialized and then assigned to, otherwise values are inserted * directly. * @param args If Container is resizable, the first argument is the size of * container to return, otherwise there is no size argument. The next argument * is always the starting value. Optionally, an increment may be specified as a * final argument. */ template KBLIB_NODISCARD constexpr auto buildiota(Args&&... args) -> auto { return detail::buildiota_impl>::impl( std::forward(args)...); } /** * @brief * * @param first * @param last * @param allocator * @return Container */ template KBLIB_NODISCARD auto build_copy(InputIt first, InputIt last, typename Container::allocator_type allocator = typename Container::allocator_type{}) -> Container { Container out(allocator); std::copy(first, last, std::back_inserter(out)); return out; } /** * @brief * * @param r * @param allocator * @return Container */ template , int> = 0> KBLIB_NODISCARD auto build_copy(Range&& r) -> Container { using std::begin; using std::end; Container out(kblib::size(r)); std::copy(begin(r), end(r), begin(out)); return out; } /** * @brief * * @param r * @param allocator * @return Container */ template KBLIB_NODISCARD auto build_copy(Range&& r, typename Container::allocator_type allocator = typename Container::allocator_type{}) -> Container { Container out(allocator); std::copy(std::begin(r), std::end(r), std::back_inserter(out)); return out; } /** * @brief * * @param first * @param last * @return Container */ template , int> = 0> KBLIB_NODISCARD constexpr auto build_copy(InputIt first, InputIt last) -> Container { Container out{}; auto pos = std::begin(out); auto end = std::end(out); for (; first != last and pos != end; ++first, ++pos) { *pos = *first; } return out; } /** * @brief * * @param r * @return Container */ template , int> = 0> KBLIB_NODISCARD constexpr auto build_copy(Range&& r) -> Container { Container out{}; auto first = std::begin(r); auto last = std::end(r); auto pos = std::begin(out); auto end = std::end(out); for (; first != last and pos != end; ++first, ++pos) { *pos = *first; } return out; } /** * @brief * * @param first * @param last * @param size * @return Container */ template , int> = 0> KBLIB_NODISCARD auto build_copy(InputIt first, InputIt last, std::size_t size) -> Container { Container out; auto pos = std::begin(out); auto end = std::end(out); for (std::size_t count = 0; count != size and first != last and pos != end; ++first, ++pos, ++count) { *pos = *first; } return out; } /** * @brief * * @param r * @param size * @return Container */ template , int> = 0> KBLIB_NODISCARD auto build_copy(Range&& r, std::size_t size) -> Container { Container out; auto first = std::begin(r); auto last = std::end(r); auto pos = std::begin(out); auto end = std::end(out); for (std::size_t count = 0; count != size and first != last and pos != end; ++first, ++pos, ++count) { *pos = *first; } return out; } /** * @brief * * @param first * @param last * @param f * @param allocator * @return Container */ template KBLIB_NODISCARD auto build_copy_if(InputIt first, InputIt last, Predicate f, typename Container::allocator_type allocator = typename Container::allocator_type{}) -> Container { Container out(allocator); kblib::copy_if(first, last, std::back_inserter(out), f); return out; } /** * @brief * * @param first * @param count * @param allocator * @return Container */ template KBLIB_NODISCARD auto build_copy_n(InputIt first, Size count, typename Container::allocator_type allocator = typename Container::allocator_type{}) -> Container { Container out(allocator); std::copy_n(first, count, std::back_inserter(out)); return out; } /** * @brief * * @param first * @param count * @param f * @param allocator * @return Container */ template KBLIB_NODISCARD auto build_copy_n_if( InputIt first, Size count, Predicate f, typename Container::allocator_type allocator = typename Container::allocator_type{}) -> Container { Container out(allocator); kblib::copy_n_if(first, count, std::back_inserter(out), f); return out; } // transform_accumulate // transform_partial_sum } // namespace KBLIB_NS #endif // KBLIB_BUILD_H