//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03

// <iterator>
//
// template <class C> constexpr auto begin(C& c) -> decltype(c.begin());                   // constexpr since C++17
// template <class C> constexpr auto begin(const C& c) -> decltype(c.begin());             // constexpr since C++17
// template <class C> constexpr auto end(C& c) -> decltype(c.end());                       // constexpr since C++17
// template <class C> constexpr auto end(const C& c) -> decltype(c.end());                 // constexpr since C++17
//
// template <class C> constexpr auto cbegin(const C& c) noexcept(see-below) -> decltype(std::begin(c)); // C++14
// template <class C> constexpr auto cend(const C& c) noexcept(see-below) -> decltype(std::end(c));     // C++14
// template <class C> constexpr auto rbegin(C& c) -> decltype(c.rbegin());                 // C++14, constexpr since C++17
// template <class C> constexpr auto rbegin(const C& c) -> decltype(c.rbegin());           // C++14, constexpr since C++17
// template <class C> constexpr auto rend(C& c) -> decltype(c.rend());                     // C++14, constexpr since C++17
// template <class C> constexpr auto rend(const C& c) -> decltype(c.rend());               // C++14, constexpr since C++17
// template <class C> constexpr auto crbegin(const C& c) -> decltype(std::rbegin(c));      // C++14, constexpr since C++17
// template <class C> constexpr auto crend(const C& c) -> decltype(std::rend(c));          // C++14, constexpr since C++17

#include <array>
#include <cassert>
#include <iterator>
#include <list>
#include <vector>

#include "test_macros.h"

template <typename C,
          typename Iterator             = typename C::iterator,
          typename ConstIterator        = typename C::const_iterator,
          typename ReverseIterator      = typename C::reverse_iterator,
          typename ConstReverseIterator = typename C::const_reverse_iterator>
TEST_CONSTEXPR_CXX17 bool test() {
  C c         = {1, 2, 3};
  const C& cc = c;

  // std::begin(C& c) / std::end(C& c)
  {
    ASSERT_SAME_TYPE(decltype(std::begin(c)), Iterator);
    assert(std::begin(c) == c.begin());

    ASSERT_SAME_TYPE(decltype(std::end(c)), Iterator);
    assert(std::end(c) == c.end());
  }

  // std::begin(C const& c) / std::end(C const& c)
  {
    ASSERT_SAME_TYPE(decltype(std::begin(cc)), ConstIterator);
    assert(std::begin(cc) == cc.begin());

    ASSERT_SAME_TYPE(decltype(std::end(cc)), ConstIterator);
    assert(std::end(cc) == cc.end());
  }

#if TEST_STD_VER >= 14
  // std::cbegin(C const&) / std::cend(C const&)
  {
    ASSERT_SAME_TYPE(decltype(std::cbegin(cc)), ConstIterator);
    static_assert(noexcept(std::cbegin(cc)) == noexcept(std::begin(cc)), "");
    assert(std::cbegin(cc) == std::begin(cc));

    ASSERT_SAME_TYPE(decltype(std::cend(cc)), ConstIterator);
    static_assert(noexcept(std::cend(cc)) == noexcept(std::end(cc)), "");
    assert(std::cend(cc) == std::end(cc));

    // kind of overkill, but whatever
    ASSERT_SAME_TYPE(decltype(std::cbegin(c)), ConstIterator);
    static_assert(noexcept(std::cbegin(c)) == noexcept(std::begin(cc)), "");
    assert(std::cbegin(c) == std::begin(cc));

    ASSERT_SAME_TYPE(decltype(std::cend(c)), ConstIterator);
    static_assert(noexcept(std::cend(c)) == noexcept(std::end(cc)), "");
    assert(std::cend(c) == std::end(cc));
  }

  // std::rbegin(C& c) / std::rend(C& c)
  {
    ASSERT_SAME_TYPE(decltype(std::rbegin(c)), ReverseIterator);
    assert(std::rbegin(c) == c.rbegin());

    ASSERT_SAME_TYPE(decltype(std::rend(c)), ReverseIterator);
    assert(std::rend(c) == c.rend());
  }

  // std::rbegin(C const&) / std::rend(C const&)
  {
    ASSERT_SAME_TYPE(decltype(std::rbegin(cc)), ConstReverseIterator);
    assert(std::rbegin(cc) == cc.rbegin());

    ASSERT_SAME_TYPE(decltype(std::rend(cc)), ConstReverseIterator);
    assert(std::rend(cc) == cc.rend());
  }

  // std::crbegin(C const&) / std::crend(C const&)
  {
    ASSERT_SAME_TYPE(decltype(std::crbegin(cc)), ConstReverseIterator);
    assert(std::crbegin(cc) == std::rbegin(cc));

    ASSERT_SAME_TYPE(decltype(std::crend(cc)), ConstReverseIterator);
    assert(std::crend(cc) == std::rend(cc));

    // kind of overkill, but whatever
    ASSERT_SAME_TYPE(decltype(std::crbegin(c)), ConstReverseIterator);
    assert(std::crbegin(c) == std::rbegin(cc));

    ASSERT_SAME_TYPE(decltype(std::crend(c)), ConstReverseIterator);
    assert(std::crend(c) == std::rend(cc));
  }
#endif // TEST_STD_VER >= 14

  return true;
}

int main(int, char**) {
  test<std::array<int, 3>>();
  test<std::list<int>>();
  test<std::vector<int>>();

#if TEST_STD_VER >= 17
  static_assert(test<std::array<int, 3>>());
#endif

  // Note: Properly testing the conditional noexcept-ness propagation in std::cbegin and std::cend
  //       requires using C-style arrays, because those are the only ones with a noexcept std::begin
  //       and std::end inside namespace std
#if TEST_STD_VER >= 14
  {
    int a[]        = {1, 2, 3};
    auto const& ca = a;
    ASSERT_NOEXCEPT(std::cbegin(ca));
    ASSERT_NOEXCEPT(std::cend(ca));

    // kind of overkill, but whatever
    ASSERT_NOEXCEPT(std::cbegin(a));
    ASSERT_NOEXCEPT(std::cend(a));
  }

  // Make sure std::cbegin and std::cend are constexpr in C++14 too (see LWG2280).
  {
    static constexpr int a[] = {1, 2, 3};
    constexpr auto b         = std::cbegin(a);
    assert(b == a);
    constexpr auto e = std::cend(a);
    assert(e == a + 3);
  }
#endif

  return 0;
}
