//  Copyright (c) 2007-2023 Hartmut Kaiser
//  Copyright (c) 2016 Thomas Heller
//
//  SPDX-License-Identifier: BSL-1.0
//  Distributed under the Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

/// \file parallel/algorithms/for_loop.hpp
/// \page hpx::experimental::for_loop, hpx::experimental::for_loop_strided, hpx::experimental::for_loop_n, hpx::experimental::for_loop_n_strided
/// \headerfile hpx/algorithm.hpp

#pragma once

#if defined(DOXYGEN)
namespace hpx { namespace experimental {

    /// The for_loop implements loop functionality over a range specified by
    /// integral or iterator bounds. For the iterator case, these algorithms
    /// resemble for_each from the Parallelism TS, but leave to the programmer
    /// when and if to dereference the iterator.
    ///
    /// The execution of for_loop without specifying an execution policy is
    /// equivalent to specifying \a hpx::execution::seq as the execution
    /// policy.
    ///
    /// \tparam I           The type of the iteration variable. This could be
    ///                     an (forward) iterator type or an integral type.
    /// \tparam Args        A parameter pack, it's last element is a function
    ///                     object to be invoked for each iteration, the others
    ///                     have to be either conforming to the induction or
    ///                     reduction concept.
    ///
    /// \param first        Refers to the beginning of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param last         Refers to the end of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param args         The last element of this parameter pack is the
    ///                     function (object) to invoke, while the remaining
    ///                     elements of the parameter pack are instances of
    ///                     either induction or reduction objects.
    ///                     The function (or function object) which will be
    ///                     invoked for each of the elements in the sequence
    ///                     specified by [first, last) should expose a signature
    ///                     equivalent to:
    ///                     \code
    ///                     <ignored> pred(I const& a, ...);
    ///                     \endcode \n
    ///                     The signature does not need to have const&. It will
    ///                     receive the current value of the iteration variable
    ///                     and one argument for each of the induction or
    ///                     reduction objects passed to the algorithms,
    ///                     representing their current values.
    ///
    /// Requires: \a I shall be an integral type or meet the requirements
    ///           of an input iterator type. The \a args parameter pack shall
    ///           have at least one element, comprising objects returned by
    ///           invocations of \a reduction and/or \a induction function
    ///           templates followed by exactly one element invocable
    ///           element-access function, \a f. \a f shall meet the
    ///           requirements of \a MoveConstructible.
    ///
    /// Effects:  Applies \a f to each element in the input sequence, with
    ///           additional arguments corresponding to the reductions and
    ///           inductions in the \a args parameter pack. The length of the
    ///           input sequence is \a last - \a first.
    ///
    /// The first element in the input sequence is specified by \a first. Each
    /// subsequent element is generated by incrementing the previous element.
    ///
    /// \note As described in the C++ standard, arithmetic on non-random-access
    ///       iterators is performed using advance and distance.
    ///
    /// \note The order of the elements of the input sequence is important for
    ///       determining ordinal position of an application of \a f, even
    ///       though the applications themselves may be unordered.
    ///
    /// Along with an element from the input sequence, for each member of the
    /// \a args parameter pack excluding \a f, an additional argument is passed
    /// to each application of \a f as follows:
    ///
    /// If the pack member is an object returned by a call to a reduction
    /// function listed in section, then the
    /// additional argument is a reference to a view of that reduction object.
    /// If the pack member is an object returned by a call to induction, then
    /// the additional argument is the induction value for that induction object
    /// corresponding to the position of the application of \a f in the input
    /// sequence.
    ///
    /// Complexity: Applies \a f exactly once for each element of the input
    ///             sequence.
    ///
    /// Remarks: If \a f returns a result, the result is ignored.
    ///
    template <typename I, typename... Args>
    void for_loop(std::decay_t<I> first, I last, Args&&... args);

    /// The for_loop implements loop functionality over a range specified by
    /// integral or iterator bounds. For the iterator case, these algorithms
    /// resemble for_each from the Parallelism TS, but leave to the programmer
    /// when and if to dereference the iterator. Executed according to the
    /// policy.
    ///
    /// \tparam ExPolicy    The type of the execution policy to use (deduced).
    ///                     It describes the manner in which the execution
    ///                     of the algorithm may be parallelized and the manner
    ///                     in which it applies user-provided function objects.
    /// \tparam I           The type of the iteration variable. This could be
    ///                     an (forward) iterator type or an integral type.
    /// \tparam Args        A parameter pack, it's last element is a function
    ///                     object to be invoked for each iteration, the others
    ///                     have to be either conforming to the induction or
    ///                     reduction concept.
    ///
    /// \param policy       The execution policy to use for the scheduling of
    ///                     the iterations.
    /// \param first        Refers to the beginning of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param last         Refers to the end of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param args         The last element of this parameter pack is the
    ///                     function (object) to invoke, while the remaining
    ///                     elements of the parameter pack are instances of
    ///                     either induction or reduction objects.
    ///                     The function (or function object) which will be
    ///                     invoked for each of the elements in the sequence
    ///                     specified by [first, last) should expose a signature
    ///                     equivalent to:
    ///                     \code
    ///                     <ignored> pred(I const& a, ...);
    ///                     \endcode \n
    ///                     The signature does not need to have const&. It will
    ///                     receive the current value of the iteration variable
    ///                     and one argument for each of the induction or
    ///                     reduction objects passed to the algorithms,
    ///                     representing their current values.
    ///
    /// Requires: \a I shall be an integral type or meet the requirements
    ///           of an input iterator type. The \a args parameter pack shall
    ///           have at least one element, comprising objects returned by
    ///           invocations of \a reduction and/or \a induction function
    ///           templates followed by exactly one element invocable
    ///           element-access function, \a f. \a f shall meet the
    ///           requirements of \a MoveConstructible.
    ///
    /// Effects:  Applies \a f to each element in the input sequence, with
    ///           additional arguments corresponding to the reductions and
    ///           inductions in the \a args parameter pack. The length of the
    ///           input sequence is \a last - \a first.
    ///
    /// The first element in the input sequence is specified by \a first. Each
    /// subsequent element is generated by incrementing the previous element.
    ///
    /// \note As described in the C++ standard, arithmetic on non-random-access
    ///       iterators is performed using advance and distance.
    ///
    /// \note The order of the elements of the input sequence is important for
    ///       determining ordinal position of an application of \a f, even
    ///       though the applications themselves may be unordered.
    ///
    /// Along with an element from the input sequence, for each member of the
    /// \a args parameter pack excluding \a f, an additional argument is passed
    /// to each application of \a f as follows:
    ///
    /// If the pack member is an object returned by a call to a reduction
    /// function listed in section, then the
    /// additional argument is a reference to a view of that reduction object.
    /// If the pack member is an object returned by a call to induction, then
    /// the additional argument is the induction value for that induction object
    /// corresponding to the position of the application of \a f in the input
    /// sequence.
    ///
    /// Complexity: Applies \a f exactly once for each element of the input
    ///             sequence.
    ///
    /// Remarks: If \a f returns a result, the result is ignored.
    ///
    /// \returns  The \a for_loop algorithm returns a
    ///           \a hpx::future<void> if the execution policy is of
    ///           type
    ///           \a hpx::execution::sequenced_task_policy or
    ///           \a hpx::execution::parallel_task_policy and returns \a void
    ///           otherwise.
    ///
    template <typename ExPolicy, typename I, typename... Args>
    hpx::parallel::util::detail::algorithm_result_t<ExPolicy> for_loop(
        ExPolicy&& policy, std::decay_t<I> first, I last, Args&&... args);

    /// The for_loop_strided implements loop functionality over a range
    /// specified by integral or iterator bounds. For the iterator case, these
    /// algorithms resemble for_each from the Parallelism TS, but leave to the
    /// programmer when and if to dereference the iterator.
    ///
    /// The execution of for_loop without specifying an execution policy is
    /// equivalent to specifying \a hpx::execution::seq as the execution
    /// policy.
    ///
    /// \tparam I           The type of the iteration variable. This could be
    ///                     an (forward) iterator type or an integral type.
    /// \tparam S           The type of the stride variable. This should be
    ///                     an integral type.
    /// \tparam Args        A parameter pack, it's last element is a function
    ///                     object to be invoked for each iteration, the others
    ///                     have to be either conforming to the induction or
    ///                     reduction concept.
    ///
    /// \param first        Refers to the beginning of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param last         Refers to the end of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param stride       Refers to the stride of the iteration steps. This
    ///                     shall have non-zero value and shall be negative
    ///                     only if I has integral type or meets the requirements
    ///                     of a bidirectional iterator.
    /// \param args         The last element of this parameter pack is the
    ///                     function (object) to invoke, while the remaining
    ///                     elements of the parameter pack are instances of
    ///                     either induction or reduction objects.
    ///                     The function (or function object) which will be
    ///                     invoked for each of the elements in the sequence
    ///                     specified by [first, last) should expose a signature
    ///                     equivalent to:
    ///                     \code
    ///                     <ignored> pred(I const& a, ...);
    ///                     \endcode \n
    ///                     The signature does not need to have const&. It will
    ///                     receive the current value of the iteration variable
    ///                     and one argument for each of the induction or
    ///                     reduction objects passed to the algorithms,
    ///                     representing their current values.
    ///
    /// Requires: \a I shall be an integral type or meet the requirements
    ///           of an input iterator type. The \a args parameter pack shall
    ///           have at least one element, comprising objects returned by
    ///           invocations of \a reduction and/or \a induction function
    ///           templates followed by exactly one element invocable
    ///           element-access function, \a f. \a f shall meet the
    ///           requirements of \a MoveConstructible.
    ///
    /// Effects:  Applies \a f to each element in the input sequence, with
    ///           additional arguments corresponding to the reductions and
    ///           inductions in the \a args parameter pack. The length of the
    ///           input sequence is \a last - \a first.
    ///
    /// The first element in the input sequence is specified by \a first. Each
    /// subsequent element is generated by incrementing the previous element.
    ///
    /// \note As described in the C++ standard, arithmetic on non-random-access
    ///       iterators is performed using advance and distance.
    ///
    /// \note The order of the elements of the input sequence is important for
    ///       determining ordinal position of an application of \a f, even
    ///       though the applications themselves may be unordered.
    ///
    /// Along with an element from the input sequence, for each member of the
    /// \a args parameter pack excluding \a f, an additional argument is passed
    /// to each application of \a f as follows:
    ///
    /// If the pack member is an object returned by a call to a reduction
    /// function listed in section, then the
    /// additional argument is a reference to a view of that reduction object.
    /// If the pack member is an object returned by a call to induction, then
    /// the additional argument is the induction value for that induction object
    /// corresponding to the position of the application of \a f in the input
    /// sequence.
    ///
    /// Complexity: Applies \a f exactly once for each element of the input
    ///             sequence.
    ///
    /// Remarks: If \a f returns a result, the result is ignored.
    ///
    template <typename I, typename S, typename... Args>
    void for_loop_strided(
        std::decay_t<I> first, I last, S stride, Args&&... args);

    /// The for_loop_strided implements loop functionality over a range
    /// specified by integral or iterator bounds. For the iterator case, these
    /// algorithms resemble for_each from the Parallelism TS, but leave to the
    /// programmer when and if to dereference the iterator. Executed according
    /// to the policy.
    ///
    /// \tparam ExPolicy    The type of the execution policy to use (deduced).
    ///                     It describes the manner in which the execution
    ///                     of the algorithm may be parallelized and the manner
    ///                     in which it applies user-provided function objects.
    /// \tparam I           The type of the iteration variable. This could be
    ///                     an (forward) iterator type or an integral type.
    /// \tparam S           The type of the stride variable. This should be
    ///                     an integral type.
    /// \tparam Args        A parameter pack, it's last element is a function
    ///                     object to be invoked for each iteration, the others
    ///                     have to be either conforming to the induction or
    ///                     reduction concept.
    ///
    /// \param policy       The execution policy to use for the scheduling of
    ///                     the iterations.
    /// \param first        Refers to the beginning of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param last         Refers to the end of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param stride       Refers to the stride of the iteration steps. This
    ///                     shall have non-zero value and shall be negative
    ///                     only if I has integral type or meets the requirements
    ///                     of a bidirectional iterator.
    /// \param args         The last element of this parameter pack is the
    ///                     function (object) to invoke, while the remaining
    ///                     elements of the parameter pack are instances of
    ///                     either induction or reduction objects.
    ///                     The function (or function object) which will be
    ///                     invoked for each of the elements in the sequence
    ///                     specified by [first, last) should expose a signature
    ///                     equivalent to:
    ///                     \code
    ///                     <ignored> pred(I const& a, ...);
    ///                     \endcode \n
    ///                     The signature does not need to have const&. It will
    ///                     receive the current value of the iteration variable
    ///                     and one argument for each of the induction or
    ///                     reduction objects passed to the algorithms,
    ///                     representing their current values.
    ///
    /// Requires: \a I shall be an integral type or meet the requirements
    ///           of an input iterator type. The \a args parameter pack shall
    ///           have at least one element, comprising objects returned by
    ///           invocations of \a reduction and/or \a induction function
    ///           templates followed by exactly one element invocable
    ///           element-access function, \a f. \a f shall meet the
    ///           requirements of \a MoveConstructible.
    ///
    /// Effects:  Applies \a f to each element in the input sequence, with
    ///           additional arguments corresponding to the reductions and
    ///           inductions in the \a args parameter pack. The length of the
    ///           input sequence is \a last - \a first.
    ///
    /// The first element in the input sequence is specified by \a first. Each
    /// subsequent element is generated by incrementing the previous element.
    ///
    /// \note As described in the C++ standard, arithmetic on non-random-access
    ///       iterators is performed using advance and distance.
    ///
    /// \note The order of the elements of the input sequence is important for
    ///       determining ordinal position of an application of \a f, even
    ///       though the applications themselves may be unordered.
    ///
    /// Along with an element from the input sequence, for each member of the
    /// \a args parameter pack excluding \a f, an additional argument is passed
    /// to each application of \a f as follows:
    ///
    /// If the pack member is an object returned by a call to a reduction
    /// function listed in section, then the
    /// additional argument is a reference to a view of that reduction object.
    /// If the pack member is an object returned by a call to induction, then
    /// the additional argument is the induction value for that induction object
    /// corresponding to the position of the application of \a f in the input
    /// sequence.
    ///
    /// Complexity: Applies \a f exactly once for each element of the input
    ///             sequence.
    ///
    /// Remarks: If \a f returns a result, the result is ignored.
    ///
    /// \returns  The \a for_loop_strided algorithm returns a
    ///           \a hpx::future<void> if the execution policy is of
    ///           type
    ///           \a hpx::execution::sequenced_task_policy or
    ///           \a hpx::execution::parallel_task_policy and returns \a void
    ///           otherwise.
    ///
    template <typename ExPolicy, typename I, typename S, typename... Args>
    hpx::parallel::util::detail::algorithm_result_t<ExPolicy> for_loop_strided(
        ExPolicy&& policy, std::decay_t<I> first, I last, S stride,
        Args&&... args);

    /// The for_loop_n implements loop functionality over a range specified by
    /// integral or iterator bounds. For the iterator case, these algorithms
    /// resemble for_each from the Parallelism TS, but leave to the programmer
    /// when and if to dereference the iterator.
    ///
    /// The execution of for_loop_n without specifying an execution policy is
    /// equivalent to specifying \a hpx::execution::seq as the execution
    /// policy.
    ///
    /// \tparam I           The type of the iteration variable. This could be
    ///                     an (forward) iterator type or an integral type.
    /// \tparam Size        The type of a non-negative integral value specifying
    ///                     the number of items to iterate over.
    /// \tparam Args        A parameter pack, it's last element is a function
    ///                     object to be invoked for each iteration, the others
    ///                     have to be either conforming to the induction or
    ///                     reduction concept.
    ///
    /// \param first        Refers to the beginning of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param size         Refers to the number of items the algorithm will be
    ///                     applied to.
    /// \param args         The last element of this parameter pack is the
    ///                     function (object) to invoke, while the remaining
    ///                     elements of the parameter pack are instances of
    ///                     either induction or reduction objects.
    ///                     The function (or function object) which will be
    ///                     invoked for each of the elements in the sequence
    ///                     specified by [first, last) should expose a signature
    ///                     equivalent to:
    ///                     \code
    ///                     <ignored> pred(I const& a, ...);
    ///                     \endcode \n
    ///                     The signature does not need to have const&. It will
    ///                     receive the current value of the iteration variable
    ///                     and one argument for each of the induction or
    ///                     reduction objects passed to the algorithms,
    ///                     representing their current values.
    ///
    /// Requires: \a I shall be an integral type or meet the requirements
    ///           of an input iterator type. The \a args parameter pack shall
    ///           have at least one element, comprising objects returned by
    ///           invocations of \a reduction and/or \a induction function
    ///           templates followed by exactly one element invocable
    ///           element-access function, \a f. \a f shall meet the
    ///           requirements of \a MoveConstructible.
    ///
    /// Effects:  Applies \a f to each element in the input sequence, with
    ///           additional arguments corresponding to the reductions and
    ///           inductions in the \a args parameter pack. The length of the
    ///           input sequence is \a last - \a first.
    ///
    /// The first element in the input sequence is specified by \a first. Each
    /// subsequent element is generated by incrementing the previous element.
    ///
    /// \note As described in the C++ standard, arithmetic on non-random-access
    ///       iterators is performed using advance and distance.
    ///
    /// \note The order of the elements of the input sequence is important for
    ///       determining ordinal position of an application of \a f, even
    ///       though the applications themselves may be unordered.
    ///
    /// Along with an element from the input sequence, for each member of the
    /// \a args parameter pack excluding \a f, an additional argument is passed
    /// to each application of \a f as follows:
    ///
    /// If the pack member is an object returned by a call to a reduction
    /// function listed in section, then the
    /// additional argument is a reference to a view of that reduction object.
    /// If the pack member is an object returned by a call to induction, then
    /// the additional argument is the induction value for that induction object
    /// corresponding to the position of the application of \a f in the input
    /// sequence.
    ///
    /// Complexity: Applies \a f exactly once for each element of the input
    ///             sequence.
    ///
    /// Remarks: If \a f returns a result, the result is ignored.
    ///
    template <typename I, typename Size, typename... Args>
    void for_loop_n(I first, Size size, Args&&... args);

    /// The for_loop_n implements loop functionality over a range specified by
    /// integral or iterator bounds. For the iterator case, these algorithms
    /// resemble for_each from the Parallelism TS, but leave to the programmer
    /// when and if to dereference the iterator. Executed according to the
    /// policy.
    ///
    /// \tparam ExPolicy    The type of the execution policy to use (deduced).
    ///                     It describes the manner in which the execution
    ///                     of the algorithm may be parallelized and the manner
    ///                     in which it applies user-provided function objects.
    /// \tparam I           The type of the iteration variable. This could be
    ///                     an (forward) iterator type or an integral type.
    /// \tparam Size        The type of a non-negative integral value specifying
    ///                     the number of items to iterate over.
    /// \tparam Args        A parameter pack, it's last element is a function
    ///                     object to be invoked for each iteration, the others
    ///                     have to be either conforming to the induction or
    ///                     reduction concept.
    ///
    /// \param policy       The execution policy to use for the scheduling of
    ///                     the iterations.
    /// \param first        Refers to the beginning of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param size         Refers to the number of items the algorithm will be
    ///                     applied to.
    /// \param args         The last element of this parameter pack is the
    ///                     function (object) to invoke, while the remaining
    ///                     elements of the parameter pack are instances of
    ///                     either induction or reduction objects.
    ///                     The function (or function object) which will be
    ///                     invoked for each of the elements in the sequence
    ///                     specified by [first, last) should expose a signature
    ///                     equivalent to:
    ///                     \code
    ///                     <ignored> pred(I const& a, ...);
    ///                     \endcode \n
    ///                     The signature does not need to have const&. It will
    ///                     receive the current value of the iteration variable
    ///                     and one argument for each of the induction or
    ///                     reduction objects passed to the algorithms,
    ///                     representing their current values.
    ///
    /// Requires: \a I shall be an integral type or meet the requirements
    ///           of an input iterator type. The \a args parameter pack shall
    ///           have at least one element, comprising objects returned by
    ///           invocations of \a reduction and/or \a induction function
    ///           templates followed by exactly one element invocable
    ///           element-access function, \a f. \a f shall meet the
    ///           requirements of \a  MoveConstructible.
    ///
    /// Effects:  Applies \a f to each element in the input sequence, with
    ///           additional arguments corresponding to the reductions and
    ///           inductions in the \a args parameter pack. The length of the
    ///           input sequence is \a last - \a first.
    ///
    /// The first element in the input sequence is specified by \a first. Each
    /// subsequent element is generated by incrementing the previous element.
    ///
    /// \note As described in the C++ standard, arithmetic on non-random-access
    ///       iterators is performed using advance and distance.
    ///
    /// \note The order of the elements of the input sequence is important for
    ///       determining ordinal position of an application of \a f, even
    ///       though the applications themselves may be unordered.
    ///
    /// Along with an element from the input sequence, for each member of the
    /// \a args parameter pack excluding \a f, an additional argument is passed
    /// to each application of \a f as follows:
    ///
    /// If the pack member is an object returned by a call to a reduction
    /// function listed in section, then the
    /// additional argument is a reference to a view of that reduction object.
    /// If the pack member is an object returned by a call to induction, then
    /// the additional argument is the induction value for that induction object
    /// corresponding to the position of the application of \a f in the input
    /// sequence.
    ///
    /// Complexity: Applies \a f exactly once for each element of the input
    ///             sequence.
    ///
    /// Remarks: If \a f returns a result, the result is ignored.
    ///
    /// \returns  The \a for_loop_n algorithm returns a
    ///           \a hpx::future<void> if the execution policy is of
    ///           type
    ///           \a hpx::execution::sequenced_task_policy or
    ///           \a hpx::execution::parallel_task_policy and returns \a void
    ///           otherwise.
    ///
    template <typename ExPolicy, typename I, typename Size, typename... Args>
    hpx::parallel::util::detail::algorithm_result_t<ExPolicy> for_loop_n(
        ExPolicy&& policy, I first, Size size, Args&&... args);

    /// The for_loop_n_strided implements loop functionality over a range
    /// specified by integral or iterator bounds. For the iterator case, these
    /// algorithms resemble for_each from the Parallelism TS, but leave to the
    /// programmer when and if to dereference the iterator.
    ///
    /// The execution of for_loop without specifying an execution policy is
    /// equivalent to specifying \a hpx::execution::seq as the execution
    /// policy.
    ///
    /// \tparam I           The type of the iteration variable. This could be
    ///                     an (forward) iterator type or an integral type.
    /// \tparam Size        The type of a non-negative integral value specifying
    ///                     the number of items to iterate over.
    /// \tparam S           The type of the stride variable. This should be
    ///                     an integral type.
    /// \tparam Args        A parameter pack, it's last element is a function
    ///                     object to be invoked for each iteration, the others
    ///                     have to be either conforming to the induction or
    ///                     reduction concept.
    ///
    /// \param first        Refers to the beginning of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param size         Refers to the number of items the algorithm will be
    ///                     applied to.
    /// \param stride       Refers to the stride of the iteration steps. This
    ///                     shall have non-zero value and shall be negative
    ///                     only if I has integral type or meets the requirements
    ///                     of a bidirectional iterator.
    /// \param args         The last element of this parameter pack is the
    ///                     function (object) to invoke, while the remaining
    ///                     elements of the parameter pack are instances of
    ///                     either induction or reduction objects.
    ///                     The function (or function object) which will be
    ///                     invoked for each of the elements in the sequence
    ///                     specified by [first, last) should expose a signature
    ///                     equivalent to:
    ///                     \code
    ///                     <ignored> pred(I const& a, ...);
    ///                     \endcode \n
    ///                     The signature does not need to have const&. It will
    ///                     receive the current value of the iteration variable
    ///                     and one argument for each of the induction or
    ///                     reduction objects passed to the algorithms,
    ///                     representing their current values.
    ///
    /// Requires: \a I shall be an integral type or meet the requirements
    ///           of an input iterator type. The \a args parameter pack shall
    ///           have at least one element, comprising objects returned by
    ///           invocations of \a reduction and/or \a induction function
    ///           templates followed by exactly one element invocable
    ///           element-access function, \a f. \a f shall meet the
    ///           requirements of \a MoveConstructible.
    ///
    /// Effects:  Applies \a f to each element in the input sequence, with
    ///           additional arguments corresponding to the reductions and
    ///           inductions in the \a args parameter pack. The length of the
    ///           input sequence is \a last - \a first.
    ///
    /// The first element in the input sequence is specified by \a first. Each
    /// subsequent element is generated by incrementing the previous element.
    ///
    /// \note As described in the C++ standard, arithmetic on non-random-access
    ///       iterators is performed using advance and distance.
    ///
    /// \note The order of the elements of the input sequence is important for
    ///       determining ordinal position of an application of \a f, even
    ///       though the applications themselves may be unordered.
    ///
    /// Along with an element from the input sequence, for each member of the
    /// \a args parameter pack excluding \a f, an additional argument is passed
    /// to each application of \a f as follows:
    ///
    /// If the pack member is an object returned by a call to a reduction
    /// function listed in section, then the
    /// additional argument is a reference to a view of that reduction object.
    /// If the pack member is an object returned by a call to induction, then
    /// the additional argument is the induction value for that induction object
    /// corresponding to the position of the application of \a f in the input
    /// sequence.
    ///
    /// Complexity: Applies \a f exactly once for each element of the input
    ///             sequence.
    ///
    /// Remarks: If \a f returns a result, the result is ignored.
    ///
    template <typename I, typename Size, typename S, typename... Args>
    void for_loop_n_strided(I first, Size size, S stride, Args&&... args);

    /// The for_loop_n_strided implements loop functionality over a range
    /// specified by integral or iterator bounds. For the iterator case, these
    /// algorithms resemble for_each from the Parallelism TS, but leave to the
    /// programmer when and if to dereference the iterator. Executed according
    /// to the policy.
    ///
    /// \tparam ExPolicy    The type of the execution policy to use (deduced).
    ///                     It describes the manner in which the execution
    ///                     of the algorithm may be parallelized and the manner
    ///                     in which it applies user-provided function objects.
    /// \tparam I           The type of the iteration variable. This could be
    ///                     an (forward) iterator type or an integral type.
    /// \tparam Size        The type of a non-negative integral value specifying
    ///                     the number of items to iterate over.
    /// \tparam S           The type of the stride variable. This should be
    ///                     an integral type.
    /// \tparam Args        A parameter pack, it's last element is a function
    ///                     object to be invoked for each iteration, the others
    ///                     have to be either conforming to the induction or
    ///                     reduction concept.
    ///
    /// \param policy       The execution policy to use for the scheduling of
    ///                     the iterations.
    /// \param first        Refers to the beginning of the sequence of elements
    ///                     the algorithm will be applied to.
    /// \param size         Refers to the number of items the algorithm will be
    ///                     applied to.
    /// \param stride       Refers to the stride of the iteration steps. This
    ///                     shall have non-zero value and shall be negative
    ///                     only if I has integral type or meets the requirements
    ///                     of a bidirectional iterator.
    /// \param args         The last element of this parameter pack is the
    ///                     function (object) to invoke, while the remaining
    ///                     elements of the parameter pack are instances of
    ///                     either induction or reduction objects.
    ///                     The function (or function object) which will be
    ///                     invoked for each of the elements in the sequence
    ///                     specified by [first, last) should expose a signature
    ///                     equivalent to:
    ///                     \code
    ///                     <ignored> pred(I const& a, ...);
    ///                     \endcode \n
    ///                     The signature does not need to have const&. It will
    ///                     receive the current value of the iteration variable
    ///                     and one argument for each of the induction or
    ///                     reduction objects passed to the algorithms,
    ///                     representing their current values.
    ///
    /// Requires: \a I shall be an integral type or meet the requirements
    ///           of an input iterator type. The \a args parameter pack shall
    ///           have at least one element, comprising objects returned by
    ///           invocations of \a reduction and/or \a induction function
    ///           templates followed by exactly one element invocable
    ///           element-access function, \a f. \a f shall meet the
    ///           requirements of \a MoveConstructible.
    ///
    /// Effects:  Applies \a f to each element in the input sequence, with
    ///           additional arguments corresponding to the reductions and
    ///           inductions in the \a args parameter pack. The length of the
    ///           input sequence is \a last - \a first.
    ///
    /// The first element in the input sequence is specified by \a first. Each
    /// subsequent element is generated by incrementing the previous element.
    ///
    /// \note As described in the C++ standard, arithmetic on non-random-access
    ///       iterators is performed using advance and distance.
    ///
    /// \note The order of the elements of the input sequence is important for
    ///       determining ordinal position of an application of \a f, even
    ///       though the applications themselves may be unordered.
    ///
    /// Along with an element from the input sequence, for each member of the
    /// \a args parameter pack excluding \a f, an additional argument is passed
    /// to each application of \a f as follows:
    ///
    /// If the pack member is an object returned by a call to a reduction
    /// function listed in section, then the
    /// additional argument is a reference to a view of that reduction object.
    /// If the pack member is an object returned by a call to induction, then
    /// the additional argument is the induction value for that induction object
    /// corresponding to the position of the application of \a f in the input
    /// sequence.
    ///
    /// Complexity: Applies \a f exactly once for each element of the input
    ///             sequence.
    ///
    /// Remarks: If \a f returns a result, the result is ignored.
    ///
    /// \returns  The \a for_loop_n_strided algorithm returns a
    ///           \a hpx::future<void> if the execution policy is of
    ///           type
    ///           \a hpx::execution::sequenced_task_policy or
    ///           \a hpx::execution::parallel_task_policy and returns \a void
    ///           otherwise.
    ///
    template <typename ExPolicy, typename I, typename Size, typename S,
        typename... Args>
    hpx::parallel::util::detail::algorithm_result_t<ExPolicy>
    for_loop_n_strided(
        ExPolicy&& policy, I first, Size size, S stride, Args&&... args);
}}    // namespace hpx::experimental

#else

#include <hpx/config.hpp>
#include <hpx/assert.hpp>
#include <hpx/concepts/concepts.hpp>
#include <hpx/datastructures/tuple.hpp>
#include <hpx/execution/algorithms/detail/predicates.hpp>
#include <hpx/functional/detail/invoke.hpp>
#include <hpx/iterator_support/range.hpp>
#include <hpx/iterator_support/traits/is_iterator.hpp>
#include <hpx/modules/executors.hpp>
#include <hpx/modules/threading_base.hpp>
#include <hpx/parallel/algorithms/detail/dispatch.hpp>
#include <hpx/parallel/algorithms/for_loop_induction.hpp>
#include <hpx/parallel/algorithms/for_loop_reduction.hpp>
#include <hpx/parallel/util/adapt_sharing_mode.hpp>
#include <hpx/parallel/util/detail/algorithm_result.hpp>
#include <hpx/parallel/util/detail/sender_util.hpp>
#include <hpx/parallel/util/loop.hpp>
#include <hpx/parallel/util/partitioner.hpp>
#include <hpx/type_support/empty_function.hpp>
#include <hpx/type_support/pack.hpp>
#include <hpx/type_support/unused.hpp>

#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>

namespace hpx::parallel {

    // for_loop
    namespace detail {

        /// \cond NOINTERNAL

        ///////////////////////////////////////////////////////////////////////
        template <typename... Ts, std::size_t... Is>
        HPX_HOST_DEVICE HPX_FORCEINLINE constexpr void init_iteration(
            hpx::tuple<Ts...>& args, hpx::util::index_pack<Is...>,
            std::size_t part_index) noexcept
        {
            (hpx::get<Is>(args).init_iteration(part_index), ...);
        }

        template <typename... Ts, std::size_t... Is, typename F, typename B>
        HPX_HOST_DEVICE HPX_FORCEINLINE constexpr void invoke_iteration(
            hpx::tuple<Ts...>& args, hpx::util::index_pack<Is...>, F&& f,
            B part_begin)
        {
            HPX_INVOKE(HPX_FORWARD(F, f), part_begin,
                hpx::get<Is>(args).iteration_value()...);
        }

        template <typename... Ts, std::size_t... Is>
        HPX_HOST_DEVICE HPX_FORCEINLINE constexpr void next_iteration(
            hpx::tuple<Ts...>& args, hpx::util::index_pack<Is...>) noexcept
        {
            (hpx::get<Is>(args).next_iteration(), ...);
        }

        template <typename... Ts, std::size_t... Is>
        HPX_HOST_DEVICE HPX_FORCEINLINE constexpr void exit_iteration(
            hpx::tuple<Ts...>& args, hpx::util::index_pack<Is...>,
            std::size_t size) noexcept
        {
            (hpx::get<Is>(args).exit_iteration(size), ...);
        }

        ///////////////////////////////////////////////////////////////////////
        template <typename ExPolicy, typename F, typename S = void,
            typename Tuple = hpx::tuple<>>
        struct part_iterations;

        template <typename ExPolicy, typename F, typename S, typename... Ts>
        struct part_iterations<ExPolicy, F, S, hpx::tuple<Ts...>>
        {
            using fun_type = std::decay_t<F>;

            fun_type f_;
            S stride_;
            hpx::tuple<Ts...> args_;

            part_iterations() = default;    // for serialization purposes only

            template <typename F_, typename S_, typename Args>
            part_iterations(F_&& f, S_&& stride, Args&& args)
              : f_(HPX_FORWARD(F_, f))
              , stride_(HPX_FORWARD(S_, stride))
              , args_(HPX_FORWARD(Args, args))
            {
            }

            template <typename B>
            HPX_HOST_DEVICE HPX_FORCEINLINE constexpr void operator()(
                B part_begin, std::size_t part_steps, std::size_t part_index)
            {
                auto pack = hpx::util::make_index_pack_t<sizeof...(Ts)>();
                detail::init_iteration(args_, pack, part_index);

                if (stride_ == 1)
                {
                    while (part_steps-- != 0)
                    {
                        detail::invoke_iteration(args_, pack, f_, part_begin++);
                        detail::next_iteration(args_, pack);
                    }
                }
                else if (stride_ > 0)
                {
                    while (part_steps >= static_cast<std::size_t>(stride_))
                    {
                        detail::invoke_iteration(args_, pack, f_, part_begin);

                        part_begin =
                            parallel::detail::next(part_begin, stride_);
                        part_steps -= stride_;

                        detail::next_iteration(args_, pack);
                    }

                    if (part_steps != 0)
                    {
                        detail::invoke_iteration(args_, pack, f_, part_begin);
                        detail::next_iteration(args_, pack);
                    }
                }
                else
                {
                    while (part_steps >= static_cast<std::size_t>(-stride_))
                    {
                        detail::invoke_iteration(args_, pack, f_, part_begin);

                        part_begin =
                            parallel::detail::next(part_begin, stride_);
                        part_steps += stride_;

                        detail::next_iteration(args_, pack);
                    }

                    if (part_steps != 0)
                    {
                        detail::invoke_iteration(args_, pack, f_, part_begin);
                        detail::next_iteration(args_, pack);
                    }
                }
            }

            template <typename Archive>
            void serialize(Archive& ar, unsigned)
            {
                // clang-format off
                ar & f_ & stride_ & args_;
                // clang-format on
            }
        };

        template <typename ExPolicy, typename F, typename S>
        struct part_iterations<ExPolicy, F, S, hpx::tuple<>>
        {
            using fun_type = std::decay_t<F>;

            fun_type f_;
            S stride_;

            part_iterations() = default;    // for serialization purposes only

            template <typename F_,
                typename Enable = std::enable_if_t<
                    !std::is_same_v<std::decay_t<F_>, part_iterations>>>
            explicit part_iterations(F_&& f)
              : f_(HPX_FORWARD(F_, f))
              , stride_(1)
            {
            }

            template <typename F_, typename S_>
            part_iterations(F_&& f, S_&& stride)
              : f_(HPX_FORWARD(F_, f))
              , stride_(HPX_FORWARD(S_, stride))
            {
            }

            template <typename F_, typename S_, typename Args>
            part_iterations(F_&& f, S_&& stride, Args&&)
              : f_(HPX_FORWARD(F_, f))
              , stride_(HPX_FORWARD(S_, stride))
            {
            }

            template <typename B>
            HPX_HOST_DEVICE HPX_FORCEINLINE constexpr void operator()(
                B part_begin, std::size_t part_steps)
            {
                HPX_ASSERT(stride_ == 1);
                parallel::util::loop_n<std::decay_t<ExPolicy>>(
                    part_begin, part_steps, f_);
            }

            template <typename B>
            HPX_HOST_DEVICE HPX_FORCEINLINE constexpr void operator()(
                B part_begin, std::size_t part_steps, std::size_t)
            {
                if (stride_ == 1)
                {
                    (*this)(part_begin, part_steps);
                }
                else if (stride_ > 0)
                {
                    while (part_steps >= static_cast<std::size_t>(stride_))
                    {
                        HPX_INVOKE(f_, part_begin);

                        part_begin =
                            parallel::detail::next(part_begin, stride_);
                        part_steps -= stride_;
                    }

                    if (part_steps != 0)
                    {
                        HPX_INVOKE(f_, part_begin);
                    }
                }
                else
                {
                    while (part_steps >= static_cast<std::size_t>(-stride_))
                    {
                        HPX_INVOKE(f_, part_begin);

                        part_begin =
                            parallel::detail::next(part_begin, stride_);
                        part_steps += stride_;
                    }

                    if (part_steps != 0)
                    {
                        HPX_INVOKE(f_, part_begin);
                    }
                }
            }

            template <typename Archive>
            void serialize(Archive& ar, unsigned)
            {
                // clang-format off
                ar & f_ & stride_;
                // clang-format on
            }
        };

        template <typename ExPolicy, typename F, typename... Ts>
        struct part_iterations<ExPolicy, F, void, hpx::tuple<Ts...>>
        {
            using fun_type = std::decay_t<F>;

            fun_type f_;
            hpx::tuple<Ts...> args_;

            part_iterations() = default;    // for serialization purposes only

            template <typename F_, typename Args>
            part_iterations(F_&& f, Args&& args)
              : f_(HPX_FORWARD(F_, f))
              , args_(HPX_FORWARD(Args, args))
            {
            }

            template <typename B, typename E>
            HPX_HOST_DEVICE HPX_FORCEINLINE constexpr void loop_iter(
                B part_begin, E part_end, std::size_t part_index)
            {
                auto pack = hpx::util::make_index_pack_t<sizeof...(Ts)>();
                detail::init_iteration(args_, pack, part_index);

                while (part_begin != part_end)
                {
                    detail::invoke_iteration(args_, pack, f_, part_begin++);
                    detail::next_iteration(args_, pack);
                }
            }

            template <typename B>
            HPX_HOST_DEVICE HPX_FORCEINLINE constexpr void operator()(
                B part_begin, std::size_t part_steps,
                std::size_t part_index = 0)
            {
                if constexpr (hpx::traits::is_range_generator_v<B>)
                {
                    auto g = hpx::util::iterate(part_begin);
                    loop_iter(
                        hpx::util::begin(g), hpx::util::end(g), part_index);
                }
                else if constexpr (hpx::traits::is_range_v<B>)
                {
                    loop_iter(hpx::util::begin(part_begin),
                        hpx::util::end(part_begin), part_index);
                }
                else
                {
                    auto pack = hpx::util::make_index_pack_t<sizeof...(Ts)>();
                    detail::init_iteration(args_, pack, part_index);

                    while (part_steps-- != 0)
                    {
                        detail::invoke_iteration(args_, pack, f_, part_begin++);
                        detail::next_iteration(args_, pack);
                    }
                }
            }

            template <typename Archive>
            void serialize(Archive& ar, unsigned)
            {
                // clang-format off
                ar & f_ & args_;
                // clang-format on
            }
        };

        template <typename ExPolicy, typename F>
        struct part_iterations<ExPolicy, F, void, hpx::tuple<>>
        {
            using fun_type = std::decay_t<F>;

            fun_type f_;

            part_iterations() = default;    // for serialization purposes only

            template <typename F_,
                typename Enable = std::enable_if_t<
                    !std::is_same_v<std::decay_t<F_>, part_iterations>>>
            explicit part_iterations(F_&& f)
              : f_(HPX_FORWARD(F_, f))
            {
            }

            template <typename F_, typename Args>
            part_iterations(F_&& f, Args&&)
              : f_(HPX_FORWARD(F_, f))
            {
            }

            template <typename IterOrR>
            HPX_HOST_DEVICE HPX_FORCEINLINE constexpr void operator()(
                IterOrR part_begin, std::size_t part_steps, std::size_t = 0)
            {
                if constexpr (hpx::traits::is_range_generator_v<IterOrR>)
                {
                    auto g = hpx::util::iterate(part_begin);
                    parallel::util::loop_ind<std::decay_t<ExPolicy>>(
                        hpx::util::begin(g), hpx::util::end(g), f_);
                }
                else if constexpr (hpx::traits::is_range_v<IterOrR>)
                {
                    parallel::util::loop(hpx::util::begin(part_begin),
                        hpx::util::end(part_begin), f_);
                }
                else
                {
                    static_assert(hpx::traits::is_iterator_v<IterOrR> ||
                        std::is_integral_v<IterOrR>);
                    parallel::util::loop_n<std::decay_t<ExPolicy>>(
                        part_begin, part_steps, f_);
                }
            }

            template <typename Archive>
            void serialize(Archive& ar, unsigned)
            {
                // clang-format off
                ar & f_;
                // clang-format on
            }
        };

        ///////////////////////////////////////////////////////////////////////
        struct for_loop_algo : public detail::algorithm<for_loop_algo>
        {
            constexpr for_loop_algo() noexcept
              : for_loop_algo::algorithm("for_loop_algo")
            {
            }

            template <typename ExPolicy, typename IterOrR, typename F>
            HPX_HOST_DEVICE static constexpr hpx::util::unused_type sequential(
                ExPolicy&&, IterOrR iter_or_r, std::size_t count, F&& f)
            {
                // perform iteration
                auto iter = part_iterations<ExPolicy, F>{HPX_FORWARD(F, f)};
                iter(iter_or_r, count);
                return {};
            }

            template <typename ExPolicy, typename IterOrR, typename Size,
                typename F, typename Arg, typename... Args>
            HPX_HOST_DEVICE static constexpr hpx::util::unused_type sequential(
                ExPolicy&&, IterOrR iter_or_r, Size size, F&& f, Arg&& arg,
                Args&&... args)
            {
                using args_type =
                    hpx::tuple<std::decay_t<Arg>, std::decay_t<Args>...>;
                args_type all_args = hpx::forward_as_tuple(
                    HPX_FORWARD(Arg, arg), HPX_FORWARD(Args, args)...);

                // perform iteration
                auto iter = part_iterations<ExPolicy, F, void, args_type>{
                    HPX_FORWARD(F, f), all_args};

                iter(iter_or_r, size, 0);

                // make sure live-out variables are properly set on return
                auto pack = hpx::util::make_index_pack_t<sizeof...(Args) + 1>();
                exit_iteration(all_args, pack, size);

                return {};
            }

            template <typename ExPolicy, typename IterOrR, typename Size,
                typename F, typename... Ts>
            static auto parallel(ExPolicy&& policy, IterOrR iter_or_r,
                Size size, F&& f, Ts&&... ts)
            {
                constexpr bool is_scheduler_policy =
                    hpx::execution_policy_has_scheduler_executor_v<ExPolicy>;

                if constexpr (!is_scheduler_policy)
                {
                    if (size == 0)
                    {
                        return util::detail::algorithm_result<ExPolicy>::get();
                    }
                }

                if constexpr (sizeof...(Ts) == 0)
                {
                    if constexpr (hpx::is_async_execution_policy_v<ExPolicy> ||
                        is_scheduler_policy)
                    {
                        return util::detail::algorithm_result<ExPolicy>::get(
                            util::partitioner<ExPolicy>::call(
                                HPX_FORWARD(ExPolicy, policy), iter_or_r, size,
                                part_iterations<ExPolicy, F>{HPX_FORWARD(F, f)},
                                hpx::util::empty_function{}));
                    }
                    else
                    {
                        util::partitioner<ExPolicy>::call(
                            HPX_FORWARD(ExPolicy, policy), iter_or_r, size,
                            part_iterations<ExPolicy, F>{HPX_FORWARD(F, f)},
                            hpx::util::empty_function{});
                        return util::detail::algorithm_result<ExPolicy>::get();
                    }
                }
                else
                {
                    // any of the induction or reduction operations prevent us
                    // from sharing the part_iteration between threads
                    decltype(auto) hinted_policy =
                        parallel::util::adapt_sharing_mode(
                            HPX_FORWARD(ExPolicy, policy),
                            hpx::threads::thread_sharing_hint::
                                do_not_share_function);

                    using policy_type = std::decay_t<decltype(policy)>;

                    // we need to decay copy here to properly transport
                    // everything to a GPU device
                    using args_type = hpx::tuple<std::decay_t<Ts>...>;

                    args_type args =
                        hpx::forward_as_tuple(HPX_FORWARD(Ts, ts)...);

                    return util::detail::algorithm_result<policy_type>::get(
                        util::partitioner<policy_type>::call_with_index(
                            hinted_policy, iter_or_r, size, 1,
                            part_iterations<policy_type, F, void, args_type>{
                                HPX_FORWARD(F, f), args},
                            [=](auto&&) mutable {
                                auto pack =
                                    hpx::util::make_index_pack_t<sizeof...(
                                        Ts)>();
                                // make sure live-out variables are properly set on
                                // return
                                detail::exit_iteration(args, pack, size);
                                return hpx::util::unused;
                            }));
                }
            }
        };    // namespace detail

        ///////////////////////////////////////////////////////////////////////
        struct for_loop_strided_algo
          : public detail::algorithm<for_loop_strided_algo>
        {
            constexpr for_loop_strided_algo() noexcept
              : for_loop_strided_algo::algorithm("for_loop_strided_algo")
            {
            }

            template <typename ExPolicy, typename InIter, typename S,
                typename F>
            HPX_HOST_DEVICE static constexpr hpx::util::unused_type sequential(
                ExPolicy&&, InIter first, std::size_t count, S stride, F&& f)
            {
                if (stride == 1)
                {
                    parallel::util::loop_n<std::decay_t<ExPolicy>>(
                        first, count, HPX_FORWARD(F, f));
                }
                else if (stride > 0)
                {
                    while (count >= static_cast<std::size_t>(stride))
                    {
                        HPX_INVOKE(f, first);

                        first = parallel::detail::next(first, stride);
                        count -= stride;
                    }

                    if (count != 0)
                    {
                        HPX_INVOKE(f, first);
                    }
                }
                else
                {
                    while (count >= static_cast<std::size_t>(-stride))
                    {
                        HPX_INVOKE(f, first);

                        first = parallel::detail::next(first, stride);
                        count += stride;
                    }

                    if (count != 0)
                    {
                        HPX_INVOKE(f, first);
                    }
                }

                return {};
            }

            template <typename ExPolicy, typename InIter, typename Size,
                typename S, typename F, typename Arg, typename... Args>
            HPX_HOST_DEVICE static constexpr hpx::util::unused_type sequential(
                ExPolicy&&, InIter first, Size size, S stride, F&& f, Arg&& arg,
                Args&&... args)
            {
                arg.init_iteration(0);
                (args.init_iteration(0), ...);

                std::size_t count = size;
                if (stride > 0)
                {
                    while (count >= static_cast<std::size_t>(stride))
                    {
                        HPX_INVOKE(f, first, arg.iteration_value(),
                            args.iteration_value()...);

                        first = parallel::detail::next(first, stride);
                        count -= stride;

                        arg.next_iteration();
                        (args.next_iteration(), ...);
                    }
                }
                else
                {
                    while (count >= static_cast<std::size_t>(-stride))
                    {
                        HPX_INVOKE(f, first, arg.iteration_value(),
                            args.iteration_value()...);

                        first = parallel::detail::next(first, stride);
                        count += stride;

                        arg.next_iteration();
                        (args.next_iteration(), ...);
                    }
                }

                if (count != 0)
                {
                    HPX_INVOKE(f, first, arg.iteration_value(),
                        args.iteration_value()...);
                }

                // make sure live-out variables are properly set on return
                arg.exit_iteration(size);
                (args.exit_iteration(size), ...);

                return {};
            }

            template <typename ExPolicy, typename B, typename Size, typename S,
                typename F, typename... Ts>
            static auto parallel(ExPolicy&& policy, B first, Size size,
                S stride, F&& f, Ts&&... ts)
            {
                constexpr bool is_scheduler_policy =
                    hpx::execution_policy_has_scheduler_executor_v<ExPolicy>;

                if constexpr (!is_scheduler_policy)
                {
                    if (size == 0)
                    {
                        return util::detail::algorithm_result<ExPolicy>::get();
                    }
                }

                if constexpr (sizeof...(Ts) == 0)
                {
                    if constexpr (!is_scheduler_policy)
                    {
                        if (stride == 1)
                        {
                            return util::detail::algorithm_result<ExPolicy>::
                                get(util::partitioner<ExPolicy>::call(
                                    HPX_FORWARD(ExPolicy, policy), first, size,
                                    part_iterations<ExPolicy, F, S>{
                                        HPX_FORWARD(F, f)},
                                    [](auto&&) { return hpx::util::unused; }));
                        }
                    }

                    if constexpr (hpx::is_async_execution_policy_v<ExPolicy> ||
                        is_scheduler_policy)
                    {
                        return util::detail::algorithm_result<ExPolicy>::get(
                            util::partitioner<ExPolicy>::call_with_index(
                                HPX_FORWARD(ExPolicy, policy), first, size,
                                stride,
                                part_iterations<ExPolicy, F, S>{
                                    HPX_FORWARD(F, f), stride},
                                [](auto&&) { return hpx::util::unused; }));
                    }
                    else
                    {
                        util::partitioner<ExPolicy>::call_with_index(
                            HPX_FORWARD(ExPolicy, policy), first, size, stride,
                            part_iterations<ExPolicy, F, S>{
                                HPX_FORWARD(F, f), stride},
                            [](auto&&) { return hpx::util::unused; });
                        return util::detail::algorithm_result<ExPolicy>::get();
                    }
                }
                else
                {
                    // any of the induction or reduction operations prevent us
                    // from sharing the part_iteration between threads
                    decltype(auto) hinted_policy =
                        parallel::util::adapt_sharing_mode(
                            HPX_FORWARD(ExPolicy, policy),
                            hpx::threads::thread_sharing_hint::
                                do_not_share_function);

                    using policy_type = std::decay_t<decltype(policy)>;

                    // we need to decay copy here to properly transport
                    // everything to a GPU device
                    using args_type = hpx::tuple<std::decay_t<Ts>...>;

                    args_type args =
                        hpx::forward_as_tuple(HPX_FORWARD(Ts, ts)...);

                    return util::detail::algorithm_result<policy_type>::get(
                        util::partitioner<policy_type>::call_with_index(
                            hinted_policy, first, size, stride,
                            part_iterations<policy_type, F, S, args_type>{
                                HPX_FORWARD(F, f), stride, args},
                            [=](auto&&) mutable {
                                auto pack =
                                    hpx::util::make_index_pack_t<sizeof...(
                                        Ts)>();
                                // make sure live-out variables are properly set on
                                // return
                                detail::exit_iteration(args, pack, size);
                                return hpx::util::unused;
                            }));
                }
            }
        };

        // reshuffle arguments, last argument is function object, will go first
        template <typename ExPolicy, typename B, typename E, std::size_t... Is,
            typename... Args>
        auto for_loop(ExPolicy&& policy, B first, E last,
            hpx::util::index_pack<Is...>, Args&&... args)
        {
            if constexpr (std::is_integral_v<B> && std::is_signed_v<B> &&
                std::is_integral_v<E> && std::is_signed_v<E> &&
                !hpx::execution_policy_has_scheduler_executor_v<ExPolicy>)
            {
                if (first >= last)
                {
                    return util::detail::algorithm_result<ExPolicy>::get();
                }
            }

            static_assert(
                std::is_integral_v<B> || hpx::traits::is_forward_iterator_v<B>,
                "Requires at least forward iterator or integral loop "
                "boundaries.");

            std::size_t size = parallel::detail::distance(first, last);
            auto&& t = hpx::forward_as_tuple(HPX_FORWARD(Args, args)...);

            auto f = hpx::get<sizeof...(Args) - 1>(t);
            return for_loop_algo().call(HPX_FORWARD(ExPolicy, policy), first,
                size, HPX_MOVE(f), hpx::get<Is>(t)...);
        }

        template <typename ExPolicy, typename R, std::size_t... Is,
            typename... Args>
        auto for_loop_range(ExPolicy&& policy, R r,
            hpx::util::index_pack<Is...>, Args&&... args)
        {
            constexpr bool scheduler_policy =
                hpx::execution_policy_has_scheduler_executor_v<ExPolicy>;

            if constexpr (!scheduler_policy)
            {
                if (hpx::util::empty(r))
                {
                    return util::detail::algorithm_result<ExPolicy>::get();
                }
            }

            using iterator = hpx::traits::range_iterator_t<R>;
            static_assert((hpx::traits::is_range_v<R> &&
                              hpx::traits::is_forward_iterator_v<iterator>) ||
                    (hpx::traits::is_range_generator_v<R> &&
                        hpx::traits::is_input_iterator_v<iterator>),
                "For ranges, requires at least forward iterator boundaries, "
                "for range generators requires at least input iterator "
                "boundaries.");

            std::size_t size = hpx::util::size(r);
            auto&& t = hpx::forward_as_tuple(HPX_FORWARD(Args, args)...);

            auto f = hpx::get<sizeof...(Args) - 1>(t);
            return for_loop_algo().call(HPX_FORWARD(ExPolicy, policy), r, size,
                HPX_MOVE(f), hpx::get<Is>(t)...);
        }

        // reshuffle arguments, last argument is function object, will go first
        template <typename ExPolicy, typename B, typename E, typename S,
            std::size_t... Is, typename... Args>
        auto for_loop_strided(ExPolicy&& policy, B first, E last, S stride,
            hpx::util::index_pack<Is...>, Args&&... args)
        {
            // stride shall not be zero
            HPX_ASSERT(stride != 0);

            if constexpr (std::is_integral_v<B> && std::is_signed_v<B> &&
                std::is_integral_v<E> && std::is_signed_v<E> &&
                !hpx::execution_policy_has_scheduler_executor_v<ExPolicy>)
            {
                if (first >= last)
                {
                    return util::detail::algorithm_result<ExPolicy>::get();
                }
            }

            // stride should be negative only if E is an integral type or at
            // least a bidirectional iterator
            if (stride < 0)
            {
                HPX_ASSERT(std::is_integral_v<E> ||
                    hpx::traits::is_bidirectional_iterator_v<E>);
            }

            static_assert(
                std::is_integral_v<B> || hpx::traits::is_forward_iterator_v<B>,
                "Requires at least forward iterator or integral loop "
                "boundaries.");

            std::size_t size = parallel::detail::distance(first, last);
            auto&& t = hpx::forward_as_tuple(HPX_FORWARD(Args, args)...);

            auto f = hpx::get<sizeof...(Args) - 1>(t);
            return for_loop_strided_algo().call(HPX_FORWARD(ExPolicy, policy),
                first, size, stride, HPX_MOVE(f), hpx::get<Is>(t)...);
        }

        template <typename ExPolicy, typename R, typename S, std::size_t... Is,
            typename... Args>
        auto for_loop_strided_range(ExPolicy&& policy, R r, S stride,
            hpx::util::index_pack<Is...>, Args&&... args)
        {
            // stride shall not be zero
            HPX_ASSERT(stride != 0);

            constexpr bool scheduler_policy =
                hpx::execution_policy_has_scheduler_executor_v<ExPolicy>;

            if constexpr (!scheduler_policy)
            {
                if (hpx::util::empty(r))
                {
                    return util::detail::algorithm_result<ExPolicy>::get();
                }
            }

            // stride should be negative only if R exposes at least a
            // bidirectional iterator
            if (stride < 0)
            {
                HPX_ASSERT(hpx::traits::is_bidirectional_iterator_v<
                    hpx::traits::range_category_t<R>>);
            }

            static_assert(hpx::traits::is_forward_iterator_v<
                              hpx::traits::range_category_t<R>>,
                "Requires at least forward iterator or integral loop "
                "boundaries.");

            std::size_t size = hpx::util::size(r);
            auto&& t = hpx::forward_as_tuple(HPX_FORWARD(Args, args)...);

            auto f = hpx::get<sizeof...(Args) - 1>(t);
            return for_loop_strided_algo().call(HPX_FORWARD(ExPolicy, policy),
                r, size, stride, HPX_MOVE(f), hpx::get<Is>(t)...);
        }

        // reshuffle arguments, last argument is function object, will go first
        template <typename ExPolicy, typename B, typename Size, typename S,
            std::size_t... Is, typename... Args>
        util::detail::algorithm_result_t<ExPolicy> for_loop_n(ExPolicy&& policy,
            B first, Size size, S stride, hpx::util::index_pack<Is...>,
            Args&&... args)
        {
            // stride shall not be zero
            HPX_ASSERT(stride != 0);

            // stride should be negative only if E is an integral type or at
            // least a bidirectional iterator
            if (stride < 0)
            {
                HPX_ASSERT(std::is_integral_v<B> ||
                    hpx::traits::is_bidirectional_iterator_v<B>);
            }

            static_assert(
                std::is_integral_v<B> || hpx::traits::is_forward_iterator_v<B>,
                "Requires at least forward iterator or integral loop "
                "boundaries.");

            auto&& t = hpx::forward_as_tuple(HPX_FORWARD(Args, args)...);

            auto f = hpx::get<sizeof...(Args) - 1>(t);
            return for_loop_strided_algo().call(HPX_FORWARD(ExPolicy, policy),
                first, size, stride, HPX_MOVE(f), hpx::get<Is>(t)...);
        }
        /// \endcond
    }    // namespace detail
}    // namespace hpx::parallel

namespace hpx::experimental {

    ///////////////////////////////////////////////////////////////////////////
    inline constexpr struct for_loop_t final
      : hpx::detail::tag_parallel_algorithm<for_loop_t>
    {
    private:
        // clang-format off
        template <typename ExPolicy, typename I, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::is_execution_policy_v<ExPolicy> &&
                (hpx::traits::is_iterator_v<I> || std::is_integral_v<I>)
            )>
        // clang-format on
        friend decltype(auto) tag_fallback_invoke(hpx::experimental::for_loop_t,
            ExPolicy&& policy, std::decay_t<I> first, I last, Args&&... args)
        {
            static_assert(sizeof...(Args) >= 1,
                "for_loop must be called with at least a function object");

            using hpx::util::make_index_pack_t;
            return hpx::parallel::detail::for_loop(
                HPX_FORWARD(ExPolicy, policy), first, last,
                make_index_pack_t<sizeof...(Args) - 1>(),
                HPX_FORWARD(Args, args)...);
        }

        // clang-format off
        template <typename I, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::traits::is_iterator_v<I> || std::is_integral_v<I>
            )>
        // clang-format on
        friend void tag_fallback_invoke(hpx::experimental::for_loop_t,
            std::decay_t<I> first, I last, Args&&... args)
        {
            static_assert(sizeof...(Args) >= 1,
                "for_loop must be called with at least a function object");

            using hpx::util::make_index_pack_t;
            return hpx::parallel::detail::for_loop(hpx::execution::seq, first,
                last, make_index_pack_t<sizeof...(Args) - 1>(),
                HPX_FORWARD(Args, args)...);
        }
    } for_loop{};

    ///////////////////////////////////////////////////////////////////////////
    inline constexpr struct for_loop_strided_t final
      : hpx::detail::tag_parallel_algorithm<for_loop_strided_t>
    {
    private:
        // clang-format off
        template <typename ExPolicy, typename I, typename S, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::is_execution_policy_v<ExPolicy> &&
                std::is_integral_v<S> &&
                (hpx::traits::is_iterator_v<I> || std::is_integral_v<I>)
            )>
        // clang-format on
        friend hpx::parallel::util::detail::algorithm_result_t<ExPolicy>
        tag_fallback_invoke(hpx::experimental::for_loop_strided_t,
            ExPolicy&& policy, std::decay_t<I> first, I last, S stride,
            Args&&... args)
        {
            static_assert(sizeof...(Args) >= 1,
                "for_loop_strided must be called with at least a function "
                "object");

            using hpx::util::make_index_pack_t;
            return hpx::parallel::detail::for_loop_strided(
                HPX_FORWARD(ExPolicy, policy), first, last, stride,
                make_index_pack_t<sizeof...(Args) - 1>(),
                HPX_FORWARD(Args, args)...);
        }

        // clang-format off
        template <typename I, typename S, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                std::is_integral_v<S> &&
                (hpx::traits::is_iterator_v<I> || std::is_integral_v<I>)
            )>
        // clang-format on
        friend void tag_fallback_invoke(hpx::experimental::for_loop_strided_t,
            std::decay_t<I> first, I last, S stride, Args&&... args)
        {
            static_assert(sizeof...(Args) >= 1,
                "for_loop_strided must be called with at least a function "
                "object");

            using hpx::util::make_index_pack_t;
            return hpx::parallel::detail::for_loop_strided(hpx::execution::seq,
                first, last, stride, make_index_pack_t<sizeof...(Args) - 1>(),
                HPX_FORWARD(Args, args)...);
        }
    } for_loop_strided{};

    ///////////////////////////////////////////////////////////////////////////
    inline constexpr struct for_loop_n_t final
      : hpx::detail::tag_parallel_algorithm<for_loop_n_t>
    {
    private:
        // clang-format off
        template <typename ExPolicy, typename I, typename Size,
            typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::is_execution_policy_v<ExPolicy> &&
                std::is_integral_v<Size> &&
                (hpx::traits::is_iterator_v<I> || std::is_integral_v<I>)
            )>
        // clang-format on
        friend hpx::parallel::util::detail::algorithm_result_t<ExPolicy>
        tag_fallback_invoke(hpx::experimental::for_loop_n_t, ExPolicy&& policy,
            I first, Size size, Args&&... args)
        {
            static_assert(sizeof...(Args) >= 1,
                "for_loop_n must be called with at least a function object");

            using hpx::util::make_index_pack_t;
            return hpx::parallel::detail::for_loop_n(
                HPX_FORWARD(ExPolicy, policy), first, size, 1,
                make_index_pack_t<sizeof...(Args) - 1>(),
                HPX_FORWARD(Args, args)...);
        }

        // clang-format off
        template <typename I, typename Size, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                std::is_integral_v<Size> &&
                (hpx::traits::is_iterator_v<I> || std::is_integral_v<I>)
            )>
        // clang-format on
        friend void tag_fallback_invoke(
            hpx::experimental::for_loop_n_t, I first, Size size, Args&&... args)
        {
            static_assert(sizeof...(Args) >= 1,
                "for_loop_n must be called with at least a function object");

            using hpx::util::make_index_pack_t;
            return hpx::parallel::detail::for_loop_n(hpx::execution::seq, first,
                size, 1, make_index_pack_t<sizeof...(Args) - 1>(),
                HPX_FORWARD(Args, args)...);
        }
    } for_loop_n{};

    ///////////////////////////////////////////////////////////////////////////
    inline constexpr struct for_loop_n_strided_t final
      : hpx::detail::tag_parallel_algorithm<for_loop_n_strided_t>
    {
    private:
        // clang-format off
        template <typename ExPolicy, typename I, typename Size, typename S,
            typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::is_execution_policy_v<ExPolicy> &&
                std::is_integral_v<Size> &&
                std::is_integral_v<S> &&
                (hpx::traits::is_iterator_v<I> || std::is_integral_v<I>)
            )>
        // clang-format on
        friend hpx::parallel::util::detail::algorithm_result_t<ExPolicy>
        tag_fallback_invoke(hpx::experimental::for_loop_n_strided_t,
            ExPolicy&& policy, I first, Size size, S stride, Args&&... args)
        {
            static_assert(sizeof...(Args) >= 1,
                "for_loop_n_strided must be called with at least a function "
                "object");

            using hpx::util::make_index_pack_t;
            return hpx::parallel::detail::for_loop_n(
                HPX_FORWARD(ExPolicy, policy), first, size, stride,
                make_index_pack_t<sizeof...(Args) - 1>(),
                HPX_FORWARD(Args, args)...);
        }

        // clang-format off
        template <typename I, typename Size, typename S, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                std::is_integral_v<Size> &&
                std::is_integral_v<S> &&
                (hpx::traits::is_iterator_v<I> || std::is_integral_v<I>)
            )>
        // clang-format on
        friend void tag_fallback_invoke(hpx::experimental::for_loop_n_strided_t,
            I first, Size size, S stride, Args&&... args)
        {
            static_assert(sizeof...(Args) >= 1,
                "for_loop_n_strided must be called with at least a function "
                "object");

            using hpx::util::make_index_pack_t;
            return hpx::parallel::detail::for_loop_n(hpx::execution::seq, first,
                size, stride, make_index_pack_t<sizeof...(Args) - 1>(),
                HPX_FORWARD(Args, args)...);
        }
    } for_loop_n_strided{};
}    // namespace hpx::experimental

namespace hpx {

    HPX_DEPRECATED_V(1, 8,
        "hpx::for_loop is deprecated. Please use "
        "hpx::experimental::for_loop instead.")
    inline constexpr hpx::experimental::for_loop_t for_loop{};

    HPX_DEPRECATED_V(1, 8,
        "hpx::for_loop_n is deprecated. Please use "
        "hpx::experimental::for_loop_n instead.")
    inline constexpr hpx::experimental::for_loop_n_t for_loop_n{};

    HPX_DEPRECATED_V(1, 8,
        "hpx::for_loop_strided is deprecated. Please use "
        "hpx::experimental::for_loop_strided instead.")
    inline constexpr hpx::experimental::for_loop_strided_t for_loop_strided{};

    HPX_DEPRECATED_V(1, 8,
        "hpx::for_loop_n_strided is deprecated. Please use "
        "hpx::experimental::for_loop_n_strided instead.")
    inline constexpr hpx::experimental::for_loop_n_strided_t
        for_loop_n_strided{};
}    // namespace hpx

#if defined(HPX_HAVE_THREAD_DESCRIPTION)
namespace hpx::traits {

    template <typename ExPolicy, typename F, typename S, typename Tuple>
    struct get_function_address<
        hpx::parallel::detail::part_iterations<ExPolicy, F, S, Tuple>>
    {
        static constexpr std::size_t call(
            hpx::parallel::detail::part_iterations<ExPolicy, F, S, Tuple> const&
                f) noexcept
        {
            return get_function_address<std::decay_t<F>>::call(f.f_);
        }
    };

    template <typename ExPolicy, typename F, typename S, typename Tuple>
    struct get_function_annotation<
        hpx::parallel::detail::part_iterations<ExPolicy, F, S, Tuple>>
    {
        static constexpr char const* call(
            hpx::parallel::detail::part_iterations<ExPolicy, F, S, Tuple> const&
                f) noexcept
        {
            return get_function_annotation<std::decay_t<F>>::call(f.f_);
        }
    };

#if HPX_HAVE_ITTNOTIFY != 0 && !defined(HPX_HAVE_APEX)
    template <typename ExPolicy, typename F, typename S, typename Tuple>
    struct get_function_annotation_itt<
        hpx::parallel::detail::part_iterations<ExPolicy, F, S, Tuple>>
    {
        static util::itt::string_handle call(
            hpx::parallel::detail::part_iterations<ExPolicy, F, S, Tuple> const&
                f) noexcept
        {
            return get_function_annotation_itt<std::decay_t<F>>::call(f.f_);
        }
    };
#endif
}    // namespace hpx::traits
#endif
#endif
