// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Copyright (C) Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// This file incorporates work covered by the following copyright and permission
// notice:
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
//
//===----------------------------------------------------------------------===//

#ifndef _ITERATOR_UTILS_H
#define _ITERATOR_UTILS_H

#include "test_config.h"

// File contains common utilities for testing with different types of iterators
namespace TestUtils
{
// ForwardIterator is like type Iterator, but restricted to be a forward iterator.
// Only the forward iterator signatures that are necessary for tests are present.
// Post-increment in particular is deliberately omitted since our templates should avoid using it
// because of efficiency considerations.
template <typename Iterator, typename IteratorTag>
class ForwardIterator
{
  public:
    using iterator_category = IteratorTag;
    using value_type = typename std::iterator_traits<Iterator>::value_type;
    using difference_type = typename std::iterator_traits<Iterator>::difference_type;
    using pointer = typename std::iterator_traits<Iterator>::pointer;
    using reference = typename std::iterator_traits<Iterator>::reference;

  protected:
    Iterator my_iterator;

  public:
    ForwardIterator() = default;
    explicit ForwardIterator(Iterator i) : my_iterator(i) {}
    reference operator*() const { return *my_iterator; }
    pointer operator->() const
    {
        if constexpr (::std::is_pointer_v<Iterator>)
        {
            return my_iterator;
        }
        else
        {
            return my_iterator.operator->();
        }
    }
    
    ForwardIterator&
    operator++()
    {
        ++my_iterator;
        return *this;
    }
    ForwardIterator operator++(std::int32_t)
    {
        auto retval = *this;
        my_iterator++;
        return retval;
    }
    friend bool
    operator==(const ForwardIterator& i, const ForwardIterator& j)
    {
        return i.my_iterator == j.my_iterator;
    }
    friend bool
    operator!=(const ForwardIterator& i, const ForwardIterator& j)
    {
        return i.my_iterator != j.my_iterator;
    }

    Iterator
    iterator() const
    {
        return my_iterator;
    }
};

template <typename Iterator, typename IteratorTag>
class BidirectionalIterator : public ForwardIterator<Iterator, IteratorTag>
{
    using base_type = ForwardIterator<Iterator, IteratorTag>;

  public:
    BidirectionalIterator() = default;
    explicit BidirectionalIterator(Iterator i) : base_type(i) {}
    BidirectionalIterator(const base_type& i) : base_type(i.iterator()) {}

    BidirectionalIterator&
    operator++()
    {
        ++base_type::my_iterator;
        return *this;
    }
    BidirectionalIterator&
    operator--()
    {
        --base_type::my_iterator;
        return *this;
    }
    BidirectionalIterator operator++(std::int32_t)
    {
        auto retval = *this;
        base_type::my_iterator++;
        return retval;
    }
    BidirectionalIterator operator--(std::int32_t)
    {
        auto retval = *this;
        base_type::my_iterator--;
        return retval;
    }
};

//============================================================================
// Adapters for creating different types of iterators.
//
// In this block we implemented some adapters for creating different types of iterators.
// It's needed for extending the unit testing of Parallel STL algorithms.
// We have adapters for iterators with different tags (forward_iterator_tag, bidirectional_iterator_tag), reverse iterators.
// The input iterator should be const or non-const, non-reverse random access iterator.
// Iterator creates in "MakeIterator":
// firstly, iterator is "packed" by "IteratorTypeAdapter" (creating forward or bidirectional iterator)
// then iterator is "packed" by "ReverseAdapter" (if it's possible)
// So, from input iterator we may create, for example, reverse bidirectional iterator.
// "Main" functor for testing iterators is named "invoke_on_all_iterator_types".

// Base adapter
template <typename Iterator>
struct BaseAdapter
{
    using iterator_type = Iterator;
    iterator_type
    operator()(Iterator it)
    {
        return it;
    }
};

// Check if the iterator is reverse iterator
// Note: it works only for iterators that created by ::std::reverse_iterator
template <typename NotReverseIterator>
struct is_reverse : ::std::false_type
{
};

template <typename Iterator>
struct is_reverse<::std::reverse_iterator<Iterator>> : ::std::true_type
{
};

template <typename Iterator>
inline constexpr bool is_reverse_v = is_reverse<Iterator>::value;

// Reverse adapter
template <typename Iterator, typename IsReverse>
struct ReverseAdapter
{
    using iterator_type = std::reverse_iterator<Iterator>;
    iterator_type
    operator()(Iterator it)
    {
        return ::std::make_reverse_iterator(it);
    }
};

// Non-reverse adapter
template <typename Iterator>
struct ReverseAdapter<Iterator, ::std::false_type> : BaseAdapter<Iterator>
{
};

// Iterator adapter by type (by default ::std::random_access_iterator_tag)
template <typename Iterator, typename IteratorTag>
struct IteratorTypeAdapter : BaseAdapter<Iterator>
{
};

// Iterator adapter for forward iterator
template <typename Iterator>
struct IteratorTypeAdapter<Iterator, ::std::forward_iterator_tag>
{
    using iterator_type = ForwardIterator<Iterator, std::forward_iterator_tag>;
    iterator_type
    operator()(Iterator it)
    {
        return iterator_type(it);
    }
};

// Iterator adapter for bidirectional iterator
template <typename Iterator>
struct IteratorTypeAdapter<Iterator, ::std::bidirectional_iterator_tag>
{
    using iterator_type = BidirectionalIterator<Iterator, std::bidirectional_iterator_tag>;
    iterator_type
    operator()(Iterator it)
    {
        return iterator_type(it);
    }
};

//For creating iterator with new type
template <typename InputIterator, typename IteratorTag, typename IsReverse>
struct MakeIterator
{
    using IterByType = IteratorTypeAdapter<InputIterator, IteratorTag>;
    using ReverseIter = ReverseAdapter<typename IterByType::iterator_type, IsReverse>;

    typename ReverseIter::iterator_type
    operator()(InputIterator it)
    {
        return ReverseIter()(IterByType()(it));
    }
};

// Useful constant variables
constexpr ::std::size_t GuardSize = 5;
template <typename T>
constexpr T sizeLimit = 1000;
// Construction below is needed for using SFINAE-friendliness that available in C++17

template <typename Iter, typename Void = void> // local iterator_traits for non-iterators
struct iterator_traits_
{
};

template <typename Iter> // For iterators
struct iterator_traits_<Iter, ::std::enable_if_t<!::std::is_void_v<typename Iter::iterator_category>>>
{
    using iterator_category = typename Iter::iterator_category;
};

template <typename T> // For pointers
struct iterator_traits_<T*>
{
    using iterator_category = std::random_access_iterator_tag;
};

// is iterator Iter has tag Tag
template <typename Iter, typename Tag>
using is_same_iterator_category = ::std::is_same<typename iterator_traits_<Iter>::iterator_category, Tag>;

template <typename Iter, typename Tag>
inline constexpr bool is_same_iterator_category_v = is_same_iterator_category<Iter, Tag>::value;

template <typename Tag, typename Iter>
using is_base_of_iterator_category = ::std::is_base_of<Tag, typename iterator_traits_<Iter>::iterator_category>;

template <typename Tag, typename Iter>
inline constexpr bool is_base_of_iterator_category_v = is_base_of_iterator_category<Tag, Iter>::value;

// if we run with reverse or const iterators we shouldn't test the large range
template <typename IsReverse, typename IsConst>
struct invoke_if_
{
    template <typename Op, typename... Rest>
    void
    operator()(bool is_allow, Op op, Rest&&... rest)
    {
        if (is_allow)
            op(::std::forward<Rest>(rest)...);
    }
};
template <>
struct invoke_if_<::std::false_type, ::std::false_type>
{
    template <typename Op, typename... Rest>
    void
    operator()(bool /*is_allow*/, Op op, Rest&&... rest)
    {
        op(::std::forward<Rest>(rest)...);
    }
};

// Base non_const_wrapper struct. It is used to distinguish non_const testcases
// from a regular one. For non_const testcases only compilation is checked.
struct non_const_wrapper
{
};

// Generic wrapper to specify iterator type to execute callable Op on.
// The condition can be either positive(Op is executed only with IteratorTag)
// or negative(Op is executed with every type of iterators except IteratorTag)
template <typename Op, typename IteratorTag, bool IsPositiveCondition = true>
struct non_const_wrapper_tagged : non_const_wrapper
{
    template <typename Policy, typename Iterator>
    ::std::enable_if_t<IsPositiveCondition == is_same_iterator_category_v<Iterator, IteratorTag>>
    operator()(Policy&& exec, Iterator iter)
    {
        Op()(std::forward<Policy>(exec), iter);
    }

    template <typename Policy, typename InputIterator, typename OutputIterator>
    ::std::enable_if_t<IsPositiveCondition == is_same_iterator_category_v<OutputIterator, IteratorTag>>
    operator()(Policy&& exec, InputIterator input_iter, OutputIterator out_iter)
    {
        Op()(std::forward<Policy>(exec), input_iter, out_iter);
    }

    template <typename Policy, typename Iterator>
    ::std::enable_if_t<IsPositiveCondition != is_same_iterator_category_v<Iterator, IteratorTag>>
    operator()(Policy&& /*exec*/, Iterator /*iter*/)
    {
    }

    template <typename Policy, typename InputIterator, typename OutputIterator>
    ::std::enable_if_t<IsPositiveCondition != is_same_iterator_category_v<OutputIterator, IteratorTag>>
    operator()(Policy&& /*exec*/, InputIterator /*input_iter*/, OutputIterator /*out_iter*/)
    {
    }
};

// These run_for_* structures specify with which types of iterators callable object Op
// should be executed.
template <typename Op>
struct run_for_rnd : non_const_wrapper_tagged<Op, ::std::random_access_iterator_tag>
{
};

template <typename Op>
struct run_for_rnd_bi : non_const_wrapper_tagged<Op, ::std::forward_iterator_tag, false>
{
};

template <typename Op>
struct run_for_rnd_fw : non_const_wrapper_tagged<Op, ::std::bidirectional_iterator_tag, false>
{
};

template <typename Iterator>
using is_const_iterator =
    typename ::std::is_const<::std::remove_pointer_t<typename ::std::iterator_traits<Iterator>::pointer>>;

// Invoker for different types of iterators.
template <typename IteratorTag, typename IsReverse>
struct iterator_invoker
{
    template <typename Iterator>
    using make_iterator = MakeIterator<Iterator, IteratorTag, IsReverse>;

    template <typename Iterator>
    using invoke_if = invoke_if_<IsReverse, typename is_const_iterator<Iterator>::type>;

    // A single iterator version which is used for non_const testcases
    template <typename Policy, typename Op, typename Iterator>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, Iterator>::value &&
                         ::std::is_base_of_v<non_const_wrapper, Op>>
    operator()(Policy&& exec, Op op, Iterator iter)
    {
        op(::std::forward<Policy>(exec), make_iterator<Iterator>()(iter));
    }

    // A version with 2 iterators which is used for non_const testcases
    template <typename Policy, typename Op, typename InputIterator, typename OutputIterator>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, OutputIterator>::value &&
                         ::std::is_base_of_v<non_const_wrapper, Op>>
    operator()(Policy&& exec, Op op, InputIterator input_iter, OutputIterator out_iter)
    {
        op(::std::forward<Policy>(exec), make_iterator<InputIterator>()(input_iter),
           make_iterator<OutputIterator>()(out_iter));
    }

    template <typename Policy, typename Op, typename Iterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::bidirectional_iterator_tag, Iterator>::value>
    operator()(Policy&& exec, Op op, Iterator begin, typename ::std::iterator_traits<Iterator>::difference_type n,
        Iterator expected, Rest&&... rest)
    {
        op(std::forward<Policy>(exec), make_iterator<Iterator>()(begin), n, make_iterator<Iterator>()(expected),
           std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename Iterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, Iterator>::value>
    operator()(Policy&& exec, Op op, Iterator begin, typename ::std::iterator_traits<Iterator>::difference_type n, Rest&&... rest)
    {
        invoke_if<Iterator>()(n <= sizeLimit<decltype(n)>, op, std::forward<Policy>(exec),
                              make_iterator<Iterator>()(begin), n, std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename Iterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, Iterator>::value &&
                           !::std::is_base_of_v<non_const_wrapper, Op>>
    operator()(Policy&& exec, Op op, Iterator inputBegin, Iterator inputEnd, Rest&&... rest)
    {
        invoke_if<Iterator>()(std::distance(inputBegin, inputEnd) <=
                                  sizeLimit<decltype(std::distance(inputBegin, inputEnd))>,
                              op, std::forward<Policy>(exec), make_iterator<Iterator>()(inputBegin),
                              make_iterator<Iterator>()(inputEnd), std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename InputIterator, typename OutputIterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, OutputIterator>::value>
    operator()(Policy&& exec, Op op, InputIterator inputBegin, InputIterator inputEnd, OutputIterator outputBegin,
               Rest&&... rest)
    {
        invoke_if<InputIterator>()(std::distance(inputBegin, inputEnd) <=
                                       sizeLimit<decltype(std::distance(inputBegin, inputEnd))>,
                                   op, std::forward<Policy>(exec), make_iterator<InputIterator>()(inputBegin),
                                   make_iterator<InputIterator>()(inputEnd),
                                   make_iterator<OutputIterator>()(outputBegin), std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename InputIterator, typename OutputIterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, OutputIterator>::value>
    operator()(Policy&& exec, Op op, InputIterator inputBegin, InputIterator inputEnd, OutputIterator outputBegin,
               OutputIterator outputEnd, Rest&&... rest)
    {
        invoke_if<InputIterator>()(
            std::distance(inputBegin, inputEnd) <= sizeLimit<decltype(std::distance(inputBegin, inputEnd))>, op,
            std::forward<Policy>(exec), make_iterator<InputIterator>()(inputBegin),
            make_iterator<InputIterator>()(inputEnd), make_iterator<OutputIterator>()(outputBegin),
            make_iterator<OutputIterator>()(outputEnd), std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename InputIterator1, typename InputIterator2, typename OutputIterator,
              typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, OutputIterator>::value>
    operator()(Policy&& exec, Op op, InputIterator1 inputBegin1, InputIterator1 inputEnd1, InputIterator2 inputBegin2,
               InputIterator2 inputEnd2, OutputIterator outputBegin, OutputIterator outputEnd, Rest&&... rest)
    {
        invoke_if<InputIterator1>()(
            std::distance(inputBegin1, inputEnd1) <= sizeLimit<decltype(std::distance(inputBegin1, inputEnd1))>, op,
            std::forward<Policy>(exec), make_iterator<InputIterator1>()(inputBegin1),
            make_iterator<InputIterator1>()(inputEnd1), make_iterator<InputIterator2>()(inputBegin2),
            make_iterator<InputIterator2>()(inputEnd2), make_iterator<OutputIterator>()(outputBegin),
            make_iterator<OutputIterator>()(outputEnd), std::forward<Rest>(rest)...);
    }
    
    template <typename Policy, typename Op, typename InputIterator1, typename InputIterator2, typename InputIterator3, typename OutputIterator,
              typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, OutputIterator>::value>
    operator()(Policy&& exec, Op op, InputIterator1 inputBegin1, InputIterator1 inputEnd1, InputIterator2 inputBegin2,
               InputIterator2 inputEnd2, InputIterator3 inputBegin3, InputIterator3 inputEnd3,
               OutputIterator outputBegin, OutputIterator outputEnd, Rest&&... rest)
    {
        invoke_if<InputIterator1>()(
            std::distance(inputBegin1, inputEnd1) <= sizeLimit<decltype(std::distance(inputBegin1, inputEnd1))>, op,
            std::forward<Policy>(exec), make_iterator<InputIterator1>()(inputBegin1),
            make_iterator<InputIterator1>()(inputEnd1), make_iterator<InputIterator2>()(inputBegin2),
            make_iterator<InputIterator2>()(inputEnd2), make_iterator<InputIterator3>()(inputBegin3),
            make_iterator<InputIterator3>()(inputEnd3), make_iterator<OutputIterator>()(outputBegin),
            make_iterator<OutputIterator>()(outputEnd), std::forward<Rest>(rest)...);
    }
};

// Invoker for reverse iterators only
// Note: if we run with reverse iterators we shouldn't test the large range
template <typename IteratorTag>
struct iterator_invoker<IteratorTag, /* IsReverse = */ ::std::true_type>
{

    template <typename Iterator>
    using make_iterator = MakeIterator<Iterator, IteratorTag, ::std::true_type>;

    // A single iterator version which is used for non_const testcases
    template <typename Policy, typename Op, typename Iterator>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, Iterator>::value &&
                         ::std::is_base_of_v<non_const_wrapper, Op>>
    operator()(Policy&& exec, Op op, Iterator iter)
    {
        op(std::forward<Policy>(exec), make_iterator<Iterator>()(iter));
    }

    // A version with 2 iterators which is used for non_const testcases
    template <typename Policy, typename Op, typename InputIterator, typename OutputIterator>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, OutputIterator>::value &&
                         ::std::is_base_of_v<non_const_wrapper, Op>>
    operator()(Policy&& exec, Op op, InputIterator input_iter, OutputIterator out_iter)
    {
        op(std::forward<Policy>(exec), make_iterator<InputIterator>()(input_iter),
           make_iterator<OutputIterator>()(out_iter));
    }

    template <typename Policy, typename Op, typename Iterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, Iterator>::value>
    operator()(Policy&& exec, Op op, Iterator begin, typename ::std::iterator_traits<Iterator>::difference_type n, Rest&&... rest)
    {
        if (n <= sizeLimit<decltype(n)>)
            op(std::forward<Policy>(exec), make_iterator<Iterator>()(begin + n), n, std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename Iterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, Iterator>::value>
    operator()(Policy&& exec, Op op, Iterator begin, typename ::std::iterator_traits<Iterator>::difference_type n, Iterator expected,
        Rest&&... rest)
    {
        if (n <= sizeLimit<decltype(n)>)
            op(std::forward<Policy>(exec), make_iterator<Iterator>()(begin + n), n,
               make_iterator<Iterator>()(expected + n), std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename Iterator, typename... Rest>
    ::std::enable_if_t<is_same_iterator_category_v<Iterator, ::std::bidirectional_iterator_tag>>
    operator()(Policy&& exec, Op op, Iterator begin, typename ::std::iterator_traits<Iterator>::difference_type n, Iterator expected, Rest&&... rest)
    {
        if (n <= sizeLimit<decltype(n)>)
            op(std::forward<Policy>(exec), make_iterator<Iterator>()(std::next(begin, n)), n,
               make_iterator<Iterator>()(std::next(expected, n)), std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename Iterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, Iterator>::value &&
                         !::std::is_base_of_v<non_const_wrapper, Op>>
    operator()(Policy&& exec, Op op, Iterator inputBegin, Iterator inputEnd, Rest&&... rest)
    {
        if (std::distance(inputBegin, inputEnd) <= sizeLimit<decltype(std::distance(inputBegin, inputEnd))>)
            op(std::forward<Policy>(exec), make_iterator<Iterator>()(inputEnd), make_iterator<Iterator>()(inputBegin),
               std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename InputIterator, typename OutputIterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, OutputIterator>::value>
    operator()(Policy&& exec, Op op, InputIterator inputBegin, InputIterator inputEnd, OutputIterator outputBegin,
               Rest&&... rest)
    {
        if (std::distance(inputBegin, inputEnd) <= sizeLimit<decltype(std::distance(inputBegin, inputEnd))>)
            op(std::forward<Policy>(exec), make_iterator<InputIterator>()(inputEnd),
               make_iterator<InputIterator>()(inputBegin),
               make_iterator<OutputIterator>()(outputBegin + (inputEnd - inputBegin)), std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename InputIterator, typename OutputIterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, OutputIterator>::value>
    operator()(Policy&& exec, Op op, InputIterator inputBegin, InputIterator inputEnd, OutputIterator outputBegin,
               OutputIterator outputEnd, Rest&&... rest)
    {
        if (std::distance(inputBegin, inputEnd) <= sizeLimit<decltype(std::distance(inputBegin, inputEnd))>)
            op(std::forward<Policy>(exec), make_iterator<InputIterator>()(inputEnd),
               make_iterator<InputIterator>()(inputBegin), make_iterator<OutputIterator>()(outputEnd),
               make_iterator<OutputIterator>()(outputBegin), std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename InputIterator1, typename InputIterator2, typename OutputIterator,
              typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, OutputIterator>::value>
    operator()(Policy&& exec, Op op, InputIterator1 inputBegin1, InputIterator1 inputEnd1, InputIterator2 inputBegin2,
               InputIterator2 inputEnd2, OutputIterator outputBegin, OutputIterator outputEnd, Rest&&... rest)
    {
        if (std::distance(inputBegin1, inputEnd1) <= sizeLimit<decltype(std::distance(inputBegin1, inputEnd1))>)
            op(std::forward<Policy>(exec), make_iterator<InputIterator1>()(inputEnd1),
               make_iterator<InputIterator1>()(inputBegin1), make_iterator<InputIterator2>()(inputEnd2),
               make_iterator<InputIterator2>()(inputBegin2), make_iterator<OutputIterator>()(outputEnd),
               make_iterator<OutputIterator>()(outputBegin), std::forward<Rest>(rest)...);
    }

    template <typename Policy, typename Op, typename InputIterator1, typename InputIterator2, typename InputIterator3,
              typename OutputIterator, typename... Rest>
    ::std::enable_if_t<is_base_of_iterator_category<::std::random_access_iterator_tag, OutputIterator>::value>
    operator()(Policy&& exec, Op op, InputIterator1 inputBegin1, InputIterator1 inputEnd1, InputIterator2 inputBegin2,
               InputIterator2 inputEnd2, InputIterator3 inputBegin3, InputIterator3 inputEnd3,
               OutputIterator outputBegin, OutputIterator outputEnd, Rest&&... rest)
    {
        if (std::distance(inputBegin1, inputEnd1) <= sizeLimit<decltype(std::distance(inputBegin1, inputEnd1))>)
            op(std::forward<Policy>(exec), make_iterator<InputIterator1>()(inputEnd1),
               make_iterator<InputIterator1>()(inputBegin1), make_iterator<InputIterator2>()(inputEnd2),
               make_iterator<InputIterator2>()(inputBegin2), make_iterator<InputIterator3>()(inputEnd3),
               make_iterator<InputIterator3>()(inputBegin3), make_iterator<OutputIterator>()(outputEnd),
               make_iterator<OutputIterator>()(outputBegin), std::forward<Rest>(rest)...);
    }

};

// We can't create reverse iterator from forward iterator
template <>
struct iterator_invoker<::std::forward_iterator_tag, /*isReverse=*/::std::true_type>
{
    template <typename... Rest>
    void
    operator()(Rest&&... /* rest */)
    {
    }
};

template <typename IsReverse>
struct reverse_invoker
{
    template <typename... Rest>
    void
    operator()(Rest&&... rest)
    {
        // Random-access iterator
        iterator_invoker<::std::random_access_iterator_tag, IsReverse>()(rest...);

        // Forward iterator
        iterator_invoker<::std::forward_iterator_tag, IsReverse>()(rest...);

        // Bidirectional iterator
        iterator_invoker<::std::bidirectional_iterator_tag, IsReverse>()(::std::forward<Rest>(rest)...);
    }
};

struct invoke_on_all_iterator_types
{
    template <typename... Rest>
    void
    operator()(Rest&&... rest)
    {
        reverse_invoker</* IsReverse = */ ::std::false_type>()(rest...);
        reverse_invoker</* IsReverse = */ ::std::true_type>()(::std::forward<Rest>(rest)...);
    }
};
} /* namespace TestUtils */
#endif // _ITERATOR_UTILS_H
