[libcxx-commits] [libcxx] [libc++] Implement P3168R2: Give optional range support (PR #149441)

William Tran-Viet via libcxx-commits libcxx-commits at lists.llvm.org
Thu Jul 17 20:00:27 PDT 2025


https://github.com/smallp-o-p created https://github.com/llvm/llvm-project/pull/149441

Resolves #105430

- Implement all required pieces of P3168R2
- Leverage existing `wrap_iter` class to implement the `optional` iterator type. Hardening has not been added, do we want that for `optional`?
- Update documentation to match

>From 60dc0ea0adc4d252285f1046f8a9557658a4d07b Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Mon, 14 Jul 2025 23:13:03 -0400
Subject: [PATCH] Implement P2988R12: Give optional range support

---
 libcxx/docs/FeatureTestMacroTable.rst         |   2 +-
 libcxx/docs/ReleaseNotes/21.rst               |   2 +-
 libcxx/docs/Status/Cxx2cPapers.csv            |   2 +-
 libcxx/include/__iterator/wrap_iter.h         |   2 +
 libcxx/include/optional                       |  49 +++++++
 libcxx/include/version                        |   2 +-
 libcxx/modules/std/optional.inc               |  11 +-
 .../test/libcxx/transitive_includes/cxx26.csv |  47 +++++++
 .../optional.version.compile.pass.cpp         |  16 +--
 .../version.version.compile.pass.cpp          |  16 +--
 .../std/utilities/optional/iterator.pass.cpp  | 124 ++++++++++++++++++
 .../generate_feature_test_macro_components.py |   1 -
 12 files changed, 246 insertions(+), 28 deletions(-)
 create mode 100644 libcxx/test/std/utilities/optional/iterator.pass.cpp

diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 61805726a4ff0..5114ae59d7c67 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -480,7 +480,7 @@ Status
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_not_fn``                                       ``202306L``
     ---------------------------------------------------------- -----------------
-    ``__cpp_lib_optional_range_support``                       *unimplemented*
+    ``__cpp_lib_optional_range_support``                       ``202406L``
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_out_ptr``                                      ``202311L``
     ---------------------------------------------------------- -----------------
diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst
index 6f18b61284f49..b8fd5e7221251 100644
--- a/libcxx/docs/ReleaseNotes/21.rst
+++ b/libcxx/docs/ReleaseNotes/21.rst
@@ -53,7 +53,7 @@ Implemented Papers
 - P2711R1: Making multi-param constructors of ``views`` ``explicit`` (`Github <https://github.com/llvm/llvm-project/issues/105252>`__)
 - P2770R0: Stashing stashing ``iterators`` for proper flattening (`Github <https://github.com/llvm/llvm-project/issues/105250>`__)
 - P2655R3: ``common_reference_t`` of ``reference_wrapper`` Should Be a Reference Type (`Github <https://github.com/llvm/llvm-project/issues/105260>`__)
-
+- P3168R2 Give ``std::optional`` Range Support (`Github <https://github.com/llvm/llvm-project/issues/105430>`__)
 Improvements and New Features
 -----------------------------
 
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index febb0c176f9c4..95f0d08fdde1c 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -66,7 +66,7 @@
 "`P2747R2 <https://wg21.link/P2747R2>`__","``constexpr`` placement new","2024-06 (St. Louis)","|Complete|","20",""
 "`P2997R1 <https://wg21.link/P2997R1>`__","Removing the common reference requirement from the indirectly invocable concepts","2024-06 (St. Louis)","|Complete|","19","Implemented as a DR against C++20. (MSVC STL and libstdc++ will do the same.)"
 "`P2389R2 <https://wg21.link/P2389R2>`__","``dextents`` Index Type Parameter","2024-06 (St. Louis)","|Complete|","19",""
-"`P3168R2 <https://wg21.link/P3168R2>`__","Give ``std::optional`` Range Support","2024-06 (St. Louis)","","",""
+"`P3168R2 <https://wg21.link/P3168R2>`__","Give ``std::optional`` Range Support","2024-06 (St. Louis)","","21","|Complete|"
 "`P3217R0 <https://wg21.link/P3217R0>`__","Adjoints to 'Enabling list-initialization for algorithms': find_last","2024-06 (St. Louis)","","",""
 "`P2985R0 <https://wg21.link/P2985R0>`__","A type trait for detecting virtual base classes","2024-06 (St. Louis)","|Complete|","20",""
 "`P0843R14 <https://wg21.link/P0843R14>`__","``inplace_vector``","2024-06 (St. Louis)","","",""
diff --git a/libcxx/include/__iterator/wrap_iter.h b/libcxx/include/__iterator/wrap_iter.h
index 2b5bc489dd44c..7610586ddecbb 100644
--- a/libcxx/include/__iterator/wrap_iter.h
+++ b/libcxx/include/__iterator/wrap_iter.h
@@ -117,6 +117,8 @@ class __wrap_iter {
   friend class span;
   template <class _Tp, size_t _Size>
   friend struct array;
+  template <class _Tp>
+  friend class optional;
 };
 
 template <class _Iter1>
diff --git a/libcxx/include/optional b/libcxx/include/optional
index e81bff50daad6..1292d969fc9ed 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -20,6 +20,11 @@ namespace std {
   template <class T>
     class optional;
 
+  template<class T>
+   constexpr bool ranges::enable_view<optional<T>> = true;
+  template<class T>
+   constexpr auto format_kind<optional<T>> = range_format::disabled;
+
   template<class T>
     concept is-derived-from-optional = requires(const T& t) {       // exposition only
       []<class U>(const optional<U>&){ }(t);
@@ -102,6 +107,8 @@ namespace std {
   class optional {
   public:
     using value_type = T;
+    using iterator               = implementation-defined; // see [optional.iterators]
+    using const_iterator         = implementation-defined; // see [optional.iterators]
 
     // [optional.ctor], constructors
     constexpr optional() noexcept;
@@ -135,6 +142,12 @@ namespace std {
     // [optional.swap], swap
     void swap(optional &) noexcept(see below ); // constexpr in C++20
 
+    // [optional.iterators], iterator support
+    constexpr iterator begin() noexcept;
+    constexpr const_iterator begin() const noexcept;
+    constexpr iterator end() noexcept;
+    constexpr const_iterator end() const noexcept;
+
     // [optional.observe], observers
     constexpr T const *operator->() const noexcept;
     constexpr T *operator->() noexcept;
@@ -233,6 +246,13 @@ namespace std {
 #  include <initializer_list>
 #  include <version>
 
+#  if _LIBCPP_STD_VER >= 26
+#    include <__cstddef/ptrdiff_t.h>
+#    include <__format/range_format.h>
+#    include <__iterator/wrap_iter.h>
+#    include <__ranges/enable_view.h>
+#  endif
+
 // standard-mandated includes
 
 // [optional.syn]
@@ -567,6 +587,14 @@ using __optional_sfinae_assign_base_t _LIBCPP_NODEBUG =
 template <class _Tp>
 class optional;
 
+#    if _LIBCPP_STD_VER >= 26
+template <class _Tp>
+constexpr bool ranges::enable_view<optional<_Tp>> = true;
+
+template <class _Tp>
+constexpr range_format format_kind<optional<_Tp>> = range_format::disabled;
+#    endif
+
 #    if _LIBCPP_STD_VER >= 20
 
 template <class _Tp>
@@ -586,8 +614,17 @@ class _LIBCPP_DECLSPEC_EMPTY_BASES optional
       private __optional_sfinae_assign_base_t<_Tp> {
   using __base _LIBCPP_NODEBUG = __optional_move_assign_base<_Tp>;
 
+#    if _LIBCPP_STD_VER >= 26
+  using pointer       = std::add_pointer_t<std::remove_cvref_t<_Tp>>;
+  using const_pointer = std::add_pointer_t<const std::remove_cvref_t<_Tp>>;
+#    endif
+
 public:
   using value_type = _Tp;
+#    if _LIBCPP_STD_VER >= 26
+  using iterator       = __wrap_iter<pointer>;
+  using const_iterator = __wrap_iter<const_pointer>;
+#    endif
 
   using __trivially_relocatable _LIBCPP_NODEBUG =
       conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, optional, void>;
@@ -792,6 +829,18 @@ public:
     }
   }
 
+#    if _LIBCPP_STD_VER >= 26
+  // [optional.iterators], iterator support
+  _LIBCPP_HIDE_FROM_ABI constexpr iterator begin() noexcept { return iterator(std::addressof(this->__get())); }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr const_iterator begin() const noexcept {
+    return const_iterator(std::addressof(this->__get()));
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr iterator end() noexcept { return begin() + this->has_value(); }
+  _LIBCPP_HIDE_FROM_ABI constexpr const_iterator end() const noexcept { return begin() + this->has_value(); }
+#    endif
+
   _LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<value_type const> operator->() const noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator-> called on a disengaged value");
     return std::addressof(this->__get());
diff --git a/libcxx/include/version b/libcxx/include/version
index d98049bd57046..88bceb9f5dc01 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -585,7 +585,7 @@ __cpp_lib_void_t                                        201411L <type_traits>
 # define __cpp_lib_mdspan                               202406L
 # undef  __cpp_lib_not_fn
 # define __cpp_lib_not_fn                               202306L
-// # define __cpp_lib_optional_range_support               202406L
+# define __cpp_lib_optional_range_support               202406L
 # undef  __cpp_lib_out_ptr
 # define __cpp_lib_out_ptr                              202311L
 // # define __cpp_lib_philox_engine                        202406L
diff --git a/libcxx/modules/std/optional.inc b/libcxx/modules/std/optional.inc
index 0f812bc0e24a4..9ee51117277ce 100644
--- a/libcxx/modules/std/optional.inc
+++ b/libcxx/modules/std/optional.inc
@@ -10,7 +10,12 @@
 export namespace std {
   // [optional.optional], class template optional
   using std::optional;
-
+#if _LIBCPP_STD_VER >= 26
+  // [optional.iterators], iterator support
+  namespace ranges {
+    using std::ranges::enable_view;
+  }
+#endif
   // [optional.nullopt], no-value state indicator
   using std::nullopt;
   using std::nullopt_t;
@@ -18,6 +23,10 @@ export namespace std {
   // [optional.bad.access], class bad_optional_access
   using std::bad_optional_access;
 
+#if _LIBCPP_STD_VER >= 26
+  using std::format_kind;
+#endif
+
   // [optional.relops], relational operators
   using std::operator==;
   using std::operator!=;
diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index 5f906338f4b7c..7d383d5c8e819 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -2,6 +2,7 @@ algorithm cctype
 algorithm climits
 algorithm compare
 algorithm cstdint
+algorithm cstdio
 algorithm cstring
 algorithm ctime
 algorithm cwchar
@@ -11,6 +12,8 @@ algorithm iosfwd
 algorithm limits
 algorithm optional
 algorithm ratio
+algorithm stdexcept
+algorithm string_view
 algorithm tuple
 algorithm version
 any cstdint
@@ -330,26 +333,32 @@ flat_map cctype
 flat_map climits
 flat_map compare
 flat_map cstdint
+flat_map cstdio
 flat_map cstring
 flat_map cwchar
 flat_map cwctype
 flat_map initializer_list
+flat_map iosfwd
 flat_map limits
 flat_map optional
 flat_map stdexcept
+flat_map string_view
 flat_map tuple
 flat_map version
 flat_set cctype
 flat_set climits
 flat_set compare
 flat_set cstdint
+flat_set cstdio
 flat_set cstring
 flat_set cwchar
 flat_set cwctype
 flat_set initializer_list
+flat_set iosfwd
 flat_set limits
 flat_set optional
 flat_set stdexcept
+flat_set string_view
 flat_set tuple
 flat_set version
 format array
@@ -417,13 +426,16 @@ functional array
 functional cctype
 functional compare
 functional cstdint
+functional cstdio
 functional cstring
 functional cwchar
 functional cwctype
 functional initializer_list
+functional iosfwd
 functional limits
 functional optional
 functional stdexcept
+functional string_view
 functional tuple
 functional typeinfo
 functional unordered_map
@@ -623,13 +635,16 @@ locale version
 map cctype
 map compare
 map cstdint
+map cstdio
 map cstring
 map cwchar
 map cwctype
 map initializer_list
+map iosfwd
 map limits
 map optional
 map stdexcept
+map string_view
 map tuple
 map version
 mdspan array
@@ -673,22 +688,36 @@ mutex typeinfo
 mutex version
 new version
 numbers version
+numeric cctype
 numeric climits
 numeric compare
 numeric cstdint
+numeric cstdio
 numeric cstring
 numeric ctime
+numeric cwchar
+numeric cwctype
 numeric initializer_list
+numeric iosfwd
 numeric limits
 numeric optional
 numeric ratio
+numeric stdexcept 
+numeric string_view
 numeric tuple
 numeric version
+optional cctype
 optional compare
 optional cstdint
+optional cstdio
 optional cstring
+optional cwchar
+optional cwctype
 optional initializer_list
+optional iosfwd
 optional limits
+optional stdexcept
+optional string_view
 optional version
 ostream array
 ostream bitset
@@ -806,6 +835,7 @@ ranges limits
 ranges optional
 ranges span
 ranges stdexcept
+ranges string_view
 ranges tuple
 ranges variant
 ranges version
@@ -851,12 +881,16 @@ semaphore version
 set cctype
 set compare
 set cstdint
+set cstdio
 set cstring
 set cwchar
 set cwctype
 set initializer_list
+set iosfwd
 set limits
 set optional
+set stdexcept 
+set string_view
 set tuple
 set version
 shared_mutex cerrno
@@ -1091,21 +1125,34 @@ typeindex typeinfo
 typeindex version
 typeinfo cstdint
 typeinfo version
+unordered_map cctype
 unordered_map compare
 unordered_map cstdint
+unordered_map cstdio
 unordered_map cstring
+unordered_map cwchar
+unordered_map cwctype
 unordered_map initializer_list
+unordered_map iosfwd
 unordered_map limits
 unordered_map optional
 unordered_map stdexcept
+unordered_map string_view
 unordered_map tuple
 unordered_map version
+unordered_set cctype
 unordered_set compare
 unordered_set cstdint
+unordered_set cstdio
 unordered_set cstring
+unordered_set cwchar
+unordered_set cwctype
 unordered_set initializer_list
+unordered_set iosfwd
 unordered_set limits
 unordered_set optional
+unordered_set stdexcept 
+unordered_set string_view
 unordered_set tuple
 unordered_set version
 utility compare
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/optional.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/optional.version.compile.pass.cpp
index 148a6dbc0d3e4..f5a05ee300dd7 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/optional.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/optional.version.compile.pass.cpp
@@ -152,17 +152,11 @@
 #    error "__cpp_lib_optional should have the value 202110L in c++26"
 #  endif
 
-#  if !defined(_LIBCPP_VERSION)
-#    ifndef __cpp_lib_optional_range_support
-#      error "__cpp_lib_optional_range_support should be defined in c++26"
-#    endif
-#    if __cpp_lib_optional_range_support != 202406L
-#      error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
-#    endif
-#  else
-#    ifdef __cpp_lib_optional_range_support
-#      error "__cpp_lib_optional_range_support should not be defined because it is unimplemented in libc++!"
-#    endif
+#  ifndef __cpp_lib_optional_range_support
+#    error "__cpp_lib_optional_range_support should be defined in c++26"
+#  endif
+#  if __cpp_lib_optional_range_support != 202406L
+#    error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
 #  endif
 
 #endif // TEST_STD_VER > 23
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 962688e06188a..ba445248a711f 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -7443,17 +7443,11 @@
 #    error "__cpp_lib_optional should have the value 202110L in c++26"
 #  endif
 
-#  if !defined(_LIBCPP_VERSION)
-#    ifndef __cpp_lib_optional_range_support
-#      error "__cpp_lib_optional_range_support should be defined in c++26"
-#    endif
-#    if __cpp_lib_optional_range_support != 202406L
-#      error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
-#    endif
-#  else
-#    ifdef __cpp_lib_optional_range_support
-#      error "__cpp_lib_optional_range_support should not be defined because it is unimplemented in libc++!"
-#    endif
+#  ifndef __cpp_lib_optional_range_support
+#    error "__cpp_lib_optional_range_support should be defined in c++26"
+#  endif
+#  if __cpp_lib_optional_range_support != 202406L
+#    error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
 #  endif
 
 #  ifndef __cpp_lib_out_ptr
diff --git a/libcxx/test/std/utilities/optional/iterator.pass.cpp b/libcxx/test/std/utilities/optional/iterator.pass.cpp
new file mode 100644
index 0000000000000..d9add15128ece
--- /dev/null
+++ b/libcxx/test/std/utilities/optional/iterator.pass.cpp
@@ -0,0 +1,124 @@
+//===----------------------------------------------------------------------===//
+//
+// 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: std-at-least-c++26
+
+// <optional>
+
+// template <class T> class optional::iterator;
+
+// 1: optional::iterator and optional const_iterator satisfy contiguous_iterator and random_access_iterator
+// 2. The value types and reference types for optional::iterator and optional::const_iterator are {_Tp, _Tp&} and {_Tp, const _Tp&} respectively
+// 3: The optional::begin() and optional::end() are marked noexcept.
+// 4: optionals that have a value have begin() != end(), whereas one that doesn't has begin() == end();
+// 5: The corresponding size for the following optionals is respected: has_value() == 1, !has_value() == 0
+// 6: Dereferencing an engaged optional's iterator returns the correct value.
+// 7: std::ranges::enable_view<optional<T>> == true, and std::format_kind<optional<T>> == true
+// 8: Verify that an iterator for loop counts only 1 item for an engaged optional, and 0 for an unegaged one.
+// 9: An optional with value that is reset will have a begin() == end(), then when it is reassigned a value, begin() != end(), and *begin() will contain the new value.
+
+#include <cassert>
+#include <optional>
+
+#include "test_macros.h"
+#include "check_assertion.h"
+
+constexpr int test_loop(const std::optional<char> val) {
+  int size = 0;
+  for (auto&& v : val) {
+    std::ignore = v;
+    size++;
+  }
+  return size;
+}
+
+constexpr bool test_reset() {
+  std::optional<int> val{1};
+  assert(val.begin() != val.end());
+  val.reset();
+  assert(val.begin() == val.end());
+  val = 1;
+  assert(val.begin() != val.end());
+  assert(*(val.begin()) == 1);
+  return true;
+}
+
+int main(int, char**) {
+  constexpr const std::optional<char> opt{'a'};
+  constexpr std::optional<char> unengaged_opt;
+  std::optional<char> nonconst_opt{'n'};
+
+  // 1
+  {
+    static_assert(std::contiguous_iterator<decltype(nonconst_opt.begin())>);
+    static_assert(std::contiguous_iterator<decltype(nonconst_opt.end())>);
+    static_assert(std::random_access_iterator<decltype(nonconst_opt.begin())>);
+    static_assert(std::random_access_iterator<decltype(nonconst_opt.end())>);
+
+    static_assert(std::contiguous_iterator<decltype(opt.begin())>);
+    static_assert(std::contiguous_iterator<decltype(opt.end())>);
+    static_assert(std::random_access_iterator<decltype(opt.begin())>);
+    static_assert(std::random_access_iterator<decltype(opt.end())>);
+  }
+
+  { // 2
+    static_assert(std::same_as<typename decltype(opt.begin())::value_type, char>);
+    static_assert(std::same_as<typename decltype(opt.begin())::reference, const char&>);
+    static_assert(std::same_as<typename decltype(nonconst_opt.begin())::value_type, char>);
+    static_assert(std::same_as<typename decltype(nonconst_opt.begin())::reference, char&>);
+  }
+
+  // 3
+  {
+    ASSERT_NOEXCEPT(opt.begin());
+    ASSERT_NOEXCEPT(opt.end());
+    ASSERT_NOEXCEPT(nonconst_opt.begin());
+    ASSERT_NOEXCEPT(nonconst_opt.end());
+  }
+
+  { // 4
+    static_assert(opt.begin() != opt.end());
+    static_assert(unengaged_opt.begin() == unengaged_opt.end());
+    assert(unengaged_opt.begin() == unengaged_opt.end());
+    assert(opt.begin() != opt.end());
+    assert(nonconst_opt.begin() != opt.end());
+  }
+
+  // 5
+  {
+    static_assert(std::ranges::size(opt) == 1);
+    static_assert(std::ranges::size(unengaged_opt) == 0);
+    assert(std::ranges::size(opt) == 1);
+    assert(std::ranges::size(unengaged_opt) == 0);
+  }
+
+  { // 6
+    static_assert(*opt.begin() == 'a');
+    assert(*(opt.begin()) == 'a');
+    assert(*(nonconst_opt.begin()) == 'n');
+  }
+
+  { // 7
+    static_assert(std::ranges::enable_view<std::optional<char>> == true);
+    assert(std::format_kind<std::optional<char>> == std::range_format::disabled);
+  }
+
+  { // 8
+    static_assert(test_loop(opt) == 1);
+    static_assert(test_loop(unengaged_opt) == 0);
+    assert(test_loop(opt) == 1);
+    assert(test_loop(unengaged_opt) == 0);
+  }
+
+  { // 9
+    static_assert(test_reset());
+    assert(test_reset());
+  }
+
+  return 0;
+}
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index fe175fd758726..152adfbbc52da 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1014,7 +1014,6 @@ def add_version_header(tc):
             "name": "__cpp_lib_optional_range_support",
             "values": {"c++26": 202406},  # P3168R2 Give std::optional Range Support
             "headers": ["optional"],
-            "unimplemented": True,
         },
         {
             "name": "__cpp_lib_out_ptr",



More information about the libcxx-commits mailing list