[libcxx][ranges] Add `std::ranges::single_view`.

Differential Revision: https://reviews.llvm.org/D106840

GitOrigin-RevId: 481ad59b9fa467882873dd7e45645e613cb77786
diff --git a/docs/Status/RangesPaper.csv b/docs/Status/RangesPaper.csv
index aede9e6..d0e7f5c 100644
--- a/docs/Status/RangesPaper.csv
+++ b/docs/Status/RangesPaper.csv
@@ -142,7 +142,7 @@
 `[range.take] <http://wg21.link/range.take>`_,take_view,[range.all],Zoe Carver,✅
 `[range.join] <http://wg21.link/range.join>`_,join_view,[range.all],Christopher Di Bella,Not started
 `[range.empty] <http://wg21.link/range.empty>`_,`empty_view <https://llvm.org/D103208>`_,[view.interface],Zoe Carver,✅
-`[range.single] <http://wg21.link/range.single>`_,single_view,[view.interface],Zoe Carver,In Progress
+`[range.single] <http://wg21.link/range.single>`_,single_view,[view.interface],Zoe Carver,✅
 `[range.split] <http://wg21.link/range.split>`_,split_view,[range.all],Unassigned,Not started
 `[range.counted] <http://wg21.link/range.counted>`_,view::counted,[range.subrange],Zoe Carver,Not started
 `[range.common] <http://wg21.link/range.common>`_,common_view,[range.all],Zoe Carver,✅
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index 68ac546..1be2df1 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -199,9 +199,10 @@
   __ranges/enable_view.h
   __ranges/non_propagating_cache.h
   __ranges/ref_view.h
-  __ranges/take_view.h
+  __ranges/single_view.h
   __ranges/size.h
   __ranges/subrange.h
+  __ranges/take_view.h
   __ranges/transform_view.h
   __ranges/view_interface.h
   __split_buffer
diff --git a/include/__ranges/copyable_box.h b/include/__ranges/copyable_box.h
index f2d3843..553d2b9 100644
--- a/include/__ranges/copyable_box.h
+++ b/include/__ranges/copyable_box.h
@@ -91,6 +91,10 @@
 
     _LIBCPP_HIDE_FROM_ABI constexpr _Tp const& operator*() const noexcept { return *__val_; }
     _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() noexcept { return *__val_; }
+
+    _LIBCPP_HIDE_FROM_ABI constexpr const _Tp *operator->() const noexcept { return __val_.operator->(); }
+    _LIBCPP_HIDE_FROM_ABI constexpr _Tp *operator->() noexcept { return __val_.operator->(); }
+
     _LIBCPP_HIDE_FROM_ABI constexpr bool __has_value() const noexcept { return __val_.has_value(); }
   };
 
@@ -162,6 +166,10 @@
 
     _LIBCPP_HIDE_FROM_ABI constexpr _Tp const& operator*() const noexcept { return __val_; }
     _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() noexcept { return __val_; }
+
+    _LIBCPP_HIDE_FROM_ABI constexpr const _Tp *operator->() const noexcept { return _VSTD::addressof(__val_); }
+    _LIBCPP_HIDE_FROM_ABI constexpr _Tp *operator->() noexcept { return _VSTD::addressof(__val_); }
+
     _LIBCPP_HIDE_FROM_ABI constexpr bool __has_value() const noexcept { return true; }
   };
 } // namespace ranges
diff --git a/include/__ranges/single_view.h b/include/__ranges/single_view.h
new file mode 100644
index 0000000..ef9822d
--- /dev/null
+++ b/include/__ranges/single_view.h
@@ -0,0 +1,83 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+#ifndef _LIBCPP___RANGES_SINGLE_VIEW_H
+#define _LIBCPP___RANGES_SINGLE_VIEW_H
+
+#include <__config>
+#include <__ranges/view_interface.h>
+#include <__ranges/copyable_box.h>
+#include <concepts>
+#include <type_traits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if !defined(_LIBCPP_HAS_NO_RANGES)
+
+namespace ranges {
+  template<copy_constructible _Tp>
+    requires is_object_v<_Tp>
+  class single_view : public view_interface<single_view<_Tp>> {
+    __copyable_box<_Tp> __value_;
+
+  public:
+    _LIBCPP_HIDE_FROM_ABI
+    single_view() requires default_initializable<_Tp> = default;
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr explicit single_view(const _Tp& __t) : __value_(in_place, __t) {}
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr explicit single_view(_Tp&& __t) : __value_(in_place, _VSTD::move(__t)) {}
+
+    template<class... _Args>
+      requires constructible_from<_Tp, _Args...>
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr explicit single_view(in_place_t, _Args&&... __args)
+      : __value_{in_place, _VSTD::forward<_Args>(__args)...} {}
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr _Tp* begin() noexcept { return data(); }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr const _Tp* begin() const noexcept { return data(); }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr _Tp* end() noexcept { return data() + 1; }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr const _Tp* end() const noexcept { return data() + 1; }
+
+    _LIBCPP_HIDE_FROM_ABI
+    static constexpr size_t size() noexcept { return 1; }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr _Tp* data() noexcept { return __value_.operator->(); }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr const _Tp* data() const noexcept { return __value_.operator->(); }
+  };
+
+  template<class _Tp>
+  single_view(_Tp) -> single_view<_Tp>;
+} // namespace ranges
+
+#endif // !defined(_LIBCPP_HAS_NO_RANGES)
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___RANGES_SINGLE_VIEW_H
diff --git a/include/module.modulemap b/include/module.modulemap
index cd77a4a..ee6f3c3 100644
--- a/include/module.modulemap
+++ b/include/module.modulemap
@@ -632,6 +632,7 @@
       module non_propagating_cache  { private header "__ranges/non_propagating_cache.h" }
       module ref_view               { private header "__ranges/ref_view.h"              }
       module size                   { private header "__ranges/size.h"                  }
+      module single_view            { private header "__ranges/single_view.h"           }
       module subrange               { private header "__ranges/subrange.h"              }
       module take_view              { private header "__ranges/take_view.h"             }
       module transform_view         { private header "__ranges/transform_view.h"        }
diff --git a/include/ranges b/include/ranges
index 0a78e00..f1a3f5e 100644
--- a/include/ranges
+++ b/include/ranges
@@ -162,6 +162,10 @@
 
   template<class T>
   inline constexpr bool enable_borrowed_range<take_view<T>> = enable_borrowed_range<T>;
+
+  template<copy_constructible T>
+    requires is_object_v<T>
+  class single_view;
 }
 
 */
@@ -179,9 +183,10 @@
 #include <__ranges/enable_borrowed_range.h>
 #include <__ranges/enable_view.h>
 #include <__ranges/ref_view.h>
-#include <__ranges/take_view.h>
+#include <__ranges/single_view.h>
 #include <__ranges/size.h>
 #include <__ranges/subrange.h>
+#include <__ranges/take_view.h>
 #include <__ranges/transform_view.h>
 #include <__ranges/view_interface.h>
 #include <compare>          // Required by the standard.
diff --git a/test/libcxx/diagnostics/detail.headers/ranges/single_view.module.verify.cpp b/test/libcxx/diagnostics/detail.headers/ranges/single_view.module.verify.cpp
new file mode 100644
index 0000000..eb6e276
--- /dev/null
+++ b/test/libcxx/diagnostics/detail.headers/ranges/single_view.module.verify.cpp
@@ -0,0 +1,16 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: modules-build
+
+// WARNING: This test was generated by 'generate_private_header_tests.py'
+// and should not be edited manually.
+
+// expected-error@*:* {{use of private header from outside its module: '__ranges/single_view.h'}}
+#include <__ranges/single_view.h>
diff --git a/test/libcxx/ranges/range.adaptors/range.copy.wrap/arrow.pass.cpp b/test/libcxx/ranges/range.adaptors/range.copy.wrap/arrow.pass.cpp
new file mode 100644
index 0000000..706ca9b
--- /dev/null
+++ b/test/libcxx/ranges/range.adaptors/range.copy.wrap/arrow.pass.cpp
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// T* <copyable-box>::operator->()
+// const T* <copyable-box>::operator->() const
+
+#include <ranges>
+
+#include <cassert>
+#include <type_traits>
+#include <utility> // in_place_t
+
+#include "types.h"
+
+template<class T>
+constexpr void check() {
+  // non-const version
+  {
+    std::ranges::__copyable_box<T> x(std::in_place, 10);
+    T* result = x.operator->();
+    static_assert(noexcept(x.operator->()));
+    assert(result->value == 10);
+    assert(x->value == 10);
+  }
+
+  // const version
+  {
+    std::ranges::__copyable_box<T> const x(std::in_place, 10);
+    const T* result = x.operator->();
+    static_assert(noexcept(x.operator->()));
+    assert(result->value == 10);
+    assert(x->value == 10);
+  }
+}
+
+constexpr bool test() {
+  check<CopyConstructible>(); // primary template
+  check<Copyable>(); // optimization #1
+  check<NothrowCopyConstructible>(); // optimization #2
+  return true;
+}
+
+int main(int, char**) {
+  assert(test());
+  static_assert(test());
+  return 0;
+}
diff --git a/test/std/ranges/range.access/range.prim/size.pass.cpp b/test/std/ranges/range.access/range.prim/size.pass.cpp
index 05f79b2..2ede178 100644
--- a/test/std/ranges/range.access/range.prim/size.pass.cpp
+++ b/test/std/ranges/range.access/range.prim/size.pass.cpp
@@ -32,6 +32,10 @@
   constexpr size_t size() { return 42; }
 };
 
+struct StaticSizeMember {
+  constexpr static size_t size() { return 42; }
+};
+
 static_assert(!std::is_invocable_v<RangeSizeT, const SizeMember>);
 
 struct SizeFunction {
@@ -82,6 +86,9 @@
   assert(std::ranges::size(SizeMemberSigned()) == 42);
   ASSERT_SAME_TYPE(decltype(std::ranges::size(SizeMemberSigned())), long);
 
+  assert(std::ranges::size(StaticSizeMember()) == 42);
+  ASSERT_SAME_TYPE(decltype(std::ranges::size(StaticSizeMember())), size_t);
+
   return true;
 }
 
diff --git a/test/std/ranges/range.factories/range.single.view/assign.pass.cpp b/test/std/ranges/range.factories/range.single.view/assign.pass.cpp
new file mode 100644
index 0000000..efa8af3
--- /dev/null
+++ b/test/std/ranges/range.factories/range.single.view/assign.pass.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10, gcc-11
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// Tests that <value_> is a <copyable-box>.
+
+#include <ranges>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct NotAssignable {
+  NotAssignable() = default;
+  NotAssignable(const NotAssignable&) = default;
+  NotAssignable(NotAssignable&&) = default;
+
+  NotAssignable& operator=(const NotAssignable&) = delete;
+  NotAssignable& operator=(NotAssignable&&) = delete;
+};
+
+constexpr bool test() {
+  const std::ranges::single_view<NotAssignable> a;
+  std::ranges::single_view<NotAssignable> b;
+  b = a;
+  b = std::move(a);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/test/std/ranges/range.factories/range.single.view/begin.pass.cpp b/test/std/ranges/range.factories/range.single.view/begin.pass.cpp
new file mode 100644
index 0000000..1df29e8
--- /dev/null
+++ b/test/std/ranges/range.factories/range.single.view/begin.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr T* begin() noexcept;
+// constexpr const T* begin() const noexcept;
+
+#include <ranges>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct Empty {};
+struct BigType { char buffer[64] = {10}; };
+
+constexpr bool test() {
+  {
+    auto sv = std::ranges::single_view<int>(42);
+    assert(*sv.begin() == 42);
+
+    ASSERT_SAME_TYPE(decltype(sv.begin()), int*);
+    static_assert(noexcept(sv.begin()));
+  }
+  {
+    const auto sv = std::ranges::single_view<int>(42);
+    assert(*sv.begin() == 42);
+
+    ASSERT_SAME_TYPE(decltype(sv.begin()), const int*);
+    static_assert(noexcept(sv.begin()));
+  }
+
+  {
+    auto sv = std::ranges::single_view<Empty>(Empty());
+    assert(sv.begin() != nullptr);
+
+    ASSERT_SAME_TYPE(decltype(sv.begin()), Empty*);
+  }
+  {
+    const auto sv = std::ranges::single_view<Empty>(Empty());
+    assert(sv.begin() != nullptr);
+
+    ASSERT_SAME_TYPE(decltype(sv.begin()), const Empty*);
+  }
+
+  {
+    auto sv = std::ranges::single_view<BigType>(BigType());
+    assert(sv.begin()->buffer[0] == 10);
+
+    ASSERT_SAME_TYPE(decltype(sv.begin()), BigType*);
+  }
+  {
+    const auto sv = std::ranges::single_view<BigType>(BigType());
+    assert(sv.begin()->buffer[0] == 10);
+
+    ASSERT_SAME_TYPE(decltype(sv.begin()), const BigType*);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/test/std/ranges/range.factories/range.single.view/ctad.compile.pass.cpp b/test/std/ranges/range.factories/range.single.view/ctad.compile.pass.cpp
new file mode 100644
index 0000000..aed366b
--- /dev/null
+++ b/test/std/ranges/range.factories/range.single.view/ctad.compile.pass.cpp
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<class T>
+//   single_view(T) -> single_view<T>;
+
+#include <ranges>
+
+#include <cassert>
+#include <concepts>
+
+#include "test_iterators.h"
+
+struct Empty {};
+
+static_assert(std::same_as<
+  decltype(std::ranges::single_view(Empty())),
+  std::ranges::single_view<Empty>
+>);
+
+static_assert(std::same_as<
+  decltype(std::ranges::single_view(std::declval<Empty&>())),
+  std::ranges::single_view<Empty>
+>);
+
+static_assert(std::same_as<
+  decltype(std::ranges::single_view(std::declval<Empty&&>())),
+  std::ranges::single_view<Empty>
+>);
diff --git a/test/std/ranges/range.factories/range.single.view/ctor.default.pass.cpp b/test/std/ranges/range.factories/range.single.view/ctor.default.pass.cpp
new file mode 100644
index 0000000..304926d
--- /dev/null
+++ b/test/std/ranges/range.factories/range.single.view/ctor.default.pass.cpp
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// single_view() requires default_initializable<T> = default;
+
+#include <ranges>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct BigType { char buffer[64] = {10}; };
+
+template<bool DefaultCtorEnabled>
+struct IsDefaultConstructible {
+  IsDefaultConstructible() requires DefaultCtorEnabled = default;
+};
+
+constexpr bool test() {
+  static_assert( std::default_initializable<std::ranges::single_view<IsDefaultConstructible<true>>>);
+  static_assert(!std::default_initializable<std::ranges::single_view<IsDefaultConstructible<false>>>);
+
+  {
+    std::ranges::single_view<BigType> sv;
+    assert(sv.data()->buffer[0] == 10);
+    assert(sv.size() == 1);
+  }
+  {
+    const std::ranges::single_view<BigType> sv;
+    assert(sv.data()->buffer[0] == 10);
+    assert(sv.size() == 1);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/test/std/ranges/range.factories/range.single.view/ctor.in_place.pass.cpp b/test/std/ranges/range.factories/range.single.view/ctor.in_place.pass.cpp
new file mode 100644
index 0000000..8f7f0a4
--- /dev/null
+++ b/test/std/ranges/range.factories/range.single.view/ctor.in_place.pass.cpp
@@ -0,0 +1,50 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<class... Args>
+//   requires constructible_from<T, Args...>
+// constexpr explicit single_view(in_place_t, Args&&... args);
+
+#include <ranges>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct TakesTwoInts {
+  int a_, b_;
+  constexpr TakesTwoInts(int a, int b) : a_(a), b_(b) {}
+};
+
+constexpr bool test() {
+  {
+    std::ranges::single_view<TakesTwoInts> sv(std::in_place, 1, 2);
+    assert(sv.data()->a_ == 1);
+    assert(sv.data()->b_ == 2);
+    assert(sv.size() == 1);
+  }
+  {
+    const std::ranges::single_view<TakesTwoInts> sv(std::in_place, 1, 2);
+    assert(sv.data()->a_ == 1);
+    assert(sv.data()->b_ == 2);
+    assert(sv.size() == 1);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/test/std/ranges/range.factories/range.single.view/ctor.value.pass.cpp b/test/std/ranges/range.factories/range.single.view/ctor.value.pass.cpp
new file mode 100644
index 0000000..fa95cf5
--- /dev/null
+++ b/test/std/ranges/range.factories/range.single.view/ctor.value.pass.cpp
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr explicit single_view(const T& t);
+// constexpr explicit single_view(T&& t);
+
+#include <ranges>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct Empty {};
+struct BigType { char buffer[64] = {10}; };
+
+constexpr bool test() {
+  {
+    BigType bt;
+    std::ranges::single_view<BigType> sv(bt);
+    assert(sv.data()->buffer[0] == 10);
+    assert(sv.size() == 1);
+  }
+  {
+    const BigType bt;
+    const std::ranges::single_view<BigType> sv(bt);
+    assert(sv.data()->buffer[0] == 10);
+    assert(sv.size() == 1);
+  }
+
+  {
+    BigType bt;
+    std::ranges::single_view<BigType> sv(std::move(bt));
+    assert(sv.data()->buffer[0] == 10);
+    assert(sv.size() == 1);
+  }
+  {
+    const BigType bt;
+    const std::ranges::single_view<BigType> sv(std::move(bt));
+    assert(sv.data()->buffer[0] == 10);
+    assert(sv.size() == 1);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/test/std/ranges/range.factories/range.single.view/data.pass.cpp b/test/std/ranges/range.factories/range.single.view/data.pass.cpp
new file mode 100644
index 0000000..9c59090
--- /dev/null
+++ b/test/std/ranges/range.factories/range.single.view/data.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr T* data() noexcept;
+// constexpr const T* data() const noexcept;
+
+#include <ranges>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct Empty {};
+struct BigType { char buffer[64] = {10}; };
+
+constexpr bool test() {
+  {
+    auto sv = std::ranges::single_view<int>(42);
+    assert(*sv.data() == 42);
+
+    ASSERT_SAME_TYPE(decltype(sv.data()), int*);
+    static_assert(noexcept(sv.data()));
+  }
+  {
+    const auto sv = std::ranges::single_view<int>(42);
+    assert(*sv.data() == 42);
+
+    ASSERT_SAME_TYPE(decltype(sv.data()), const int*);
+    static_assert(noexcept(sv.data()));
+  }
+
+  {
+    auto sv = std::ranges::single_view<Empty>(Empty());
+    assert(sv.data() != nullptr);
+
+    ASSERT_SAME_TYPE(decltype(sv.data()), Empty*);
+  }
+  {
+    const auto sv = std::ranges::single_view<Empty>(Empty());
+    assert(sv.data() != nullptr);
+
+    ASSERT_SAME_TYPE(decltype(sv.data()), const Empty*);
+  }
+
+  {
+    auto sv = std::ranges::single_view<BigType>(BigType());
+    assert(sv.data()->buffer[0] == 10);
+
+    ASSERT_SAME_TYPE(decltype(sv.data()), BigType*);
+  }
+  {
+    const auto sv = std::ranges::single_view<BigType>(BigType());
+    assert(sv.data()->buffer[0] == 10);
+
+    ASSERT_SAME_TYPE(decltype(sv.data()), const BigType*);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/test/std/ranges/range.factories/range.single.view/end.pass.cpp b/test/std/ranges/range.factories/range.single.view/end.pass.cpp
new file mode 100644
index 0000000..d407ca0
--- /dev/null
+++ b/test/std/ranges/range.factories/range.single.view/end.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr T* end() noexcept;
+// constexpr const T* end() const noexcept;
+
+#include <ranges>
+#include <cassert>
+
+#include "test_macros.h"
+
+struct Empty {};
+struct BigType { char buffer[64] = {10}; };
+
+constexpr bool test() {
+  {
+    auto sv = std::ranges::single_view<int>(42);
+    assert(sv.end() == sv.begin() + 1);
+
+    ASSERT_SAME_TYPE(decltype(sv.end()), int*);
+    static_assert(noexcept(sv.end()));
+  }
+  {
+    const auto sv = std::ranges::single_view<int>(42);
+    assert(sv.end() == sv.begin() + 1);
+
+    ASSERT_SAME_TYPE(decltype(sv.end()), const int*);
+    static_assert(noexcept(sv.end()));
+  }
+
+  {
+    auto sv = std::ranges::single_view<Empty>(Empty());
+    assert(sv.end() == sv.begin() + 1);
+
+    ASSERT_SAME_TYPE(decltype(sv.end()), Empty*);
+  }
+  {
+    const auto sv = std::ranges::single_view<Empty>(Empty());
+    assert(sv.end() == sv.begin() + 1);
+
+    ASSERT_SAME_TYPE(decltype(sv.end()), const Empty*);
+  }
+
+  {
+    auto sv = std::ranges::single_view<BigType>(BigType());
+    assert(sv.end() == sv.begin() + 1);
+
+    ASSERT_SAME_TYPE(decltype(sv.end()), BigType*);
+  }
+  {
+    const auto sv = std::ranges::single_view<BigType>(BigType());
+    assert(sv.end() == sv.begin() + 1);
+
+    ASSERT_SAME_TYPE(decltype(sv.end()), const BigType*);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/test/std/ranges/range.factories/range.single.view/range_concept_conformance.compile.pass.cpp b/test/std/ranges/range.factories/range.single.view/range_concept_conformance.compile.pass.cpp
new file mode 100644
index 0000000..63b817f
--- /dev/null
+++ b/test/std/ranges/range.factories/range.single.view/range_concept_conformance.compile.pass.cpp
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// Test that single_view conforms to range and view concepts.
+
+#include <ranges>
+
+#include <cassert>
+#include <concepts>
+
+#include "test_iterators.h"
+
+struct Empty {};
+
+static_assert(std::ranges::contiguous_range<std::ranges::single_view<Empty>>);
+static_assert(std::ranges::contiguous_range<const std::ranges::single_view<Empty>>);
+static_assert(std::ranges::view<std::ranges::single_view<Empty>>);
+static_assert(std::ranges::view<std::ranges::single_view<const Empty>>);
+static_assert(std::ranges::contiguous_range<const std::ranges::single_view<const Empty>>);
+static_assert(std::ranges::view<std::ranges::single_view<int>>);
+static_assert(std::ranges::view<std::ranges::single_view<const int>>);
+static_assert(std::ranges::contiguous_range<const std::ranges::single_view<const int>>);
diff --git a/test/std/ranges/range.factories/range.single.view/size.pass.cpp b/test/std/ranges/range.factories/range.single.view/size.pass.cpp
new file mode 100644
index 0000000..87054a7
--- /dev/null
+++ b/test/std/ranges/range.factories/range.single.view/size.pass.cpp
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: gcc-10
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// static constexpr size_t size() noexcept;
+
+#include <ranges>
+#include <cassert>
+
+#include "test_macros.h"
+
+constexpr bool test() {
+  {
+    auto sv = std::ranges::single_view<int>(42);
+    assert(sv.size() == 1);
+
+    ASSERT_SAME_TYPE(decltype(sv.size()), size_t);
+    static_assert(noexcept(sv.size()));
+  }
+  {
+    const auto sv = std::ranges::single_view<int>(42);
+    assert(sv.size() == 1);
+
+    ASSERT_SAME_TYPE(decltype(sv.size()), size_t);
+    static_assert(noexcept(sv.size()));
+  }
+  {
+    auto sv = std::ranges::single_view<int>(42);
+    assert(std::ranges::size(sv) == 1);
+
+    ASSERT_SAME_TYPE(decltype(std::ranges::size(sv)), size_t);
+    static_assert(noexcept(std::ranges::size(sv)));
+  }
+  {
+    const auto sv = std::ranges::single_view<int>(42);
+    assert(std::ranges::size(sv) == 1);
+
+    ASSERT_SAME_TYPE(decltype(std::ranges::size(sv)), size_t);
+    static_assert(noexcept(std::ranges::size(sv)));
+  }
+
+  // Test that it's static.
+  {
+    assert(std::ranges::single_view<int>::size() == 1);
+
+    ASSERT_SAME_TYPE(decltype(std::ranges::single_view<int>::size()), size_t);
+    static_assert(noexcept(std::ranges::single_view<int>::size()));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}