//  Copyright (c) 2020 ETH Zurich
//  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)

/// \page hpx::ranges::experimental::for_loop, hpx::ranges::experimental::for_loop_strided, hpx::ranges::experimental::for_loop_n, hpx::ranges::experimental::for_loop_n_strided
/// \headerfile hpx/algorithm.hpp

#pragma once

#if defined(DOXYGEN)
namespace hpx { namespace ranges { namespace experimental {
    // clang-format off

    /// The for_loop implements loop functionality over a range specified by
    /// iterator bounds. These algorithms resemble for_each from the
    /// Parallelism TS, but leave to the programmer when and if to dereference
    /// the iterator.
    ///
    /// \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 Iter        The type of the iteration variable (forward iterator).
    /// \tparam Sent        The type of the source sentinel (deduced). This
    ///                     sentinel type must be a sentinel for Iter.
    /// \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(Iter 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 Iter shall meet the requirements
    ///           of a forward 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 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 last - 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 Iter, typename Sent, typename... Args>
    typename hpx::parallel::util::detail::algorithm_result<
        ExPolicy>::type
    for_loop(ExPolicy&& policy, Iter first, Sent last, Args&&... args);

    /// The for_loop implements loop functionality over a range specified by
    /// iterator bounds. 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 Iter        The type of the iteration variable (input iterator).
    /// \tparam Sent        The type of the source sentinel (deduced). This
    ///                     sentinel type must be a sentinel for Iter.
    /// \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(Iter 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 Iter shall 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 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 last - 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 Iter, typename Sent, typename... Args>
    void for_loop(Iter first, Sent last, Args&&... args);

    /// The for_loop implements loop functionality over a range specified by
    /// a range. These algorithms resemble for_each from the
    /// Parallelism TS, but leave to the programmer when and if to dereference
    /// the iterator.
    ///
    /// \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 R           The type of the source range used (deduced).
    ///                     The iterators extracted from this range type must
    ///                     meet the requirements of an input iterator.
    /// \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 rng          Refers to theof 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(Rng::iterator 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 Rng::iterator shall meet the requirements
    ///           of a forward 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 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 last - 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 R, typename... Args>
    typename hpx::parallel::util::detail::algorithm_result<
        ExPolicy>::type
    for_loop(ExPolicy&& policy, R&& rng, Args&&... args);

    /// The for_loop implements loop functionality over a range specified by
    /// a range. 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 Rng         The type of the source range used (deduced).
    ///                     The iterators extracted from this range type must
    ///                     meet the requirements of an input iterator.
    /// \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 rng          Refers to theof 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(Rng::iterator 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 Rng::iterator shall 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 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 last - 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 Rng, typename... Args>
    void for_loop(Rng&& rng, Args&&... args);

     /// The for_loop_strided implements loop functionality over a range specified by
    /// iterator bounds. These algorithms resemble for_each from the
    /// Parallelism TS, but leave to the programmer when and if to dereference
    /// the iterator.
    ///
    /// \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 Iter        The type of the iteration variable (forward iterator).
    /// \tparam Sent        The type of the source sentinel (deduced). This
    ///                     sentinel type must be a sentinel for Iter.
    /// \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 Iter meets the requirements 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(Iter 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 Iter shall meet the requirements
    ///           of a forward 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 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 last - 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 Iter, typename Sent, typename S,
        typename... Args>
    hpx::parallel::util::detail::algorithm_result_t<ExPolicy>
    for_loop_strided(ExPolicy&& policy, Iter first, Sent last, S stride, Args&&... args);

    /// The for_loop_strided implements loop functionality over a range specified by
    /// iterator bounds. 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_strided without specifying an execution policy is
    /// equivalent to specifying \a hpx::execution::seq as the execution
    /// policy.
    ///
    /// \tparam Iter        The type of the iteration variable (input iterator).
    /// \tparam Sent        The type of the source sentinel (deduced). This
    ///                     sentinel type must be a sentinel for Iter.
    /// \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 Iter meets the requirements 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(Iter 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 Iter shall 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 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 last - 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 Iter, typename Sent, typename S, typename... Args>
    void for_loop_strided(Iter first, Sent last, S stride, Args&&... args);

    /// The for_loop_strided implements loop functionality over a range specified by
    /// a range. These algorithms resemble for_each from the
    /// Parallelism TS, but leave to the programmer when and if to dereference
    /// the iterator.
    ///
    /// \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 Rng         The type of the source range used (deduced).
    ///                     The iterators extracted from this range type must
    ///                     meet the requirements of an input iterator.
    /// \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 rng          Refers to theof 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 Rng::iterator meets the requirements 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(Rng::iterator 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 Rng::iterator shall meet the requirements
    ///           of a forward 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 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 last - 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 Rng, typename S, typename... Args>
    hpx::parallel::util::detail::algorithm_result_t<ExPolicy>
    for_loop_strided(ExPolicy&& policy, Rng&& rng, S stride, Args&&... args);

    /// The for_loop_strided implements loop functionality over a range specified by
    /// a range. 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_strided without specifying an execution policy is
    /// equivalent to specifying \a hpx::execution::seq as the execution
    /// policy.
    ///
    /// \tparam Rng         The type of the source range used (deduced).
    ///                     The iterators extracted from this range type must
    ///                     meet the requirements of an input iterator.
    /// \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 rng          Refers to theof 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 Rng::iterator meets the requirements 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(Rng::iterator 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 Rng::iterator shall 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 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 last - 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 Rng, typename S, typename... Args>
    void for_loop_strided(Rng&& rng, S stride, Args&&... args);
    // clang-format on
}}}    // namespace hpx::ranges::experimental

#else

#include <hpx/config.hpp>
#include <hpx/modules/concepts.hpp>
#include <hpx/modules/executors.hpp>
#include <hpx/modules/iterator_support.hpp>
#include <hpx/modules/type_support.hpp>
#include <hpx/parallel/algorithms/for_loop.hpp>
#include <hpx/parallel/util/detail/algorithm_result.hpp>
#include <hpx/parallel/util/detail/sender_util.hpp>

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

namespace hpx::ranges::experimental {

    inline constexpr struct for_loop_t final
      : hpx::detail::tag_parallel_algorithm<for_loop_t>
    {
    private:
        // clang-format off
        template <typename ExPolicy, typename Iter, typename Sent, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::is_execution_policy_v<ExPolicy> &&
                hpx::traits::is_iterator_v<Iter> &&
                hpx::traits::is_sentinel_for_v<Sent, Iter>
            )>
        // clang-format on
        friend hpx::parallel::util::detail::algorithm_result_t<ExPolicy>
        tag_fallback_invoke(hpx::ranges::experimental::for_loop_t,
            ExPolicy&& policy, Iter first, Sent 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 Iter, typename Sent, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::traits::is_iterator_v<Iter> &&
                hpx::traits::is_sentinel_for_v<Sent, Iter>
            )>
        // clang-format on
        friend void tag_fallback_invoke(hpx::ranges::experimental::for_loop_t,
            Iter first, Sent 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)...);
        }

        // clang-format off
        template <typename ExPolicy, typename R, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::is_execution_policy_v<ExPolicy> &&
                (hpx::traits::is_range_v<R> ||
                 hpx::traits::is_range_generator_v<R>)
            )>
        // clang-format on
        friend hpx::parallel::util::detail::algorithm_result_t<ExPolicy>
        tag_fallback_invoke(hpx::ranges::experimental::for_loop_t,
            ExPolicy&& policy, R&& rng, 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;
            if constexpr (hpx::traits::is_range_generator_v<R>)
            {
                return hpx::parallel::detail::for_loop_range(
                    HPX_FORWARD(ExPolicy, policy), rng,
                    make_index_pack_t<sizeof...(Args) - 1>(),
                    HPX_FORWARD(Args, args)...);
            }
            else
            {
                return hpx::parallel::detail::for_loop(
                    HPX_FORWARD(ExPolicy, policy), hpx::util::begin(rng),
                    hpx::util::end(rng),
                    make_index_pack_t<sizeof...(Args) - 1>(),
                    HPX_FORWARD(Args, args)...);
            }
        }

        // clang-format off
        template <typename Rng, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::traits::is_range_v<Rng> ||
                hpx::traits::is_range_generator_v<Rng>
            )>
        // clang-format on
        friend void tag_fallback_invoke(
            hpx::ranges::experimental::for_loop_t, Rng&& rng, 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;
            if constexpr (hpx::traits::is_range_generator_v<Rng>)
            {
                return hpx::parallel::detail::for_loop_range(
                    hpx::execution::seq, rng,
                    make_index_pack_t<sizeof...(Args) - 1>(),
                    HPX_FORWARD(Args, args)...);
            }
            else
            {
                return hpx::parallel::detail::for_loop(hpx::execution::seq,
                    hpx::util::begin(rng), hpx::util::end(rng),
                    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 Iter, typename Sent, typename S,
            typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::is_execution_policy_v<ExPolicy> &&
                std::is_integral_v<S> &&
                hpx::traits::is_iterator_v<Iter> &&
                hpx::traits::is_sentinel_for_v<Sent, Iter>
            )>
        // clang-format on
        friend parallel::util::detail::algorithm_result_t<ExPolicy>
        tag_fallback_invoke(hpx::ranges::experimental::for_loop_strided_t,
            ExPolicy&& policy, Iter first, Sent 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 Iter, typename Sent, typename S, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                std::is_integral_v<S> &&
                hpx::traits::is_iterator_v<Iter> &&
                hpx::traits::is_sentinel_for_v<Sent, Iter>
            )>
        // clang-format on
        friend void tag_fallback_invoke(
            hpx::ranges::experimental::for_loop_strided_t, Iter first,
            Sent 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)...);
        }

        // clang-format off
        template <typename ExPolicy, typename Rng, typename S, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                hpx::is_execution_policy_v<ExPolicy> &&
                std::is_integral_v<S> &&
                hpx::traits::is_range_v<Rng>
            )>
        // clang-format on
        friend hpx::parallel::util::detail::algorithm_result_t<ExPolicy>
        tag_fallback_invoke(hpx::ranges::experimental::for_loop_strided_t,
            ExPolicy&& policy, Rng&& rng, 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), hpx::util::begin(rng),
                hpx::util::end(rng), stride,
                make_index_pack_t<sizeof...(Args) - 1>(),
                HPX_FORWARD(Args, args)...);
        }

        // clang-format off
        template <typename Rng, typename S, typename... Args,
            HPX_CONCEPT_REQUIRES_(
                std::is_integral_v<S> &&
                hpx::traits::is_range_v<Rng>
            )>
        // clang-format on
        friend void tag_fallback_invoke(
            hpx::ranges::experimental::for_loop_strided_t, Rng&& rng, 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,
                hpx::util::begin(rng), hpx::util::end(rng), stride,
                make_index_pack_t<sizeof...(Args) - 1>(),
                HPX_FORWARD(Args, args)...);
        }
    } for_loop_strided{};
}    // namespace hpx::ranges::experimental

namespace hpx::ranges {

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

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

#endif    // DOXYGEN
