Resolves #171345 Implements [proposed resolution for LWG4308](https://cplusplus.github.io/LWG/issue4308) and removes `const_iterator` from `optional<T&>`, which was missed. - Constrains iterator to only be available if T is not an lvalue reference, or if it is T&, that T is an object type and is not an unbounded array - Add a partial specialization for `__optional_iterator` for `T&`, which only has the `iterator` type. - Correct a static assert message as a drive-by - Move the libcxx specific iterator test into the standard test because the standard now specifies when the iterator should be available
147 lines
5.0 KiB
C++
147 lines
5.0 KiB
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: std-at-least-c++26
|
|
|
|
// <optional>
|
|
|
|
// template <class T> class optional::iterator;
|
|
// template <class T> class optional::const_iterator;
|
|
|
|
#include <cassert>
|
|
#include <optional>
|
|
#include <ranges>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
template <typename T>
|
|
concept has_iterator = requires { typename T::iterator; };
|
|
|
|
template <typename T>
|
|
concept has_const_iterator = requires { typename T::const_iterator; };
|
|
|
|
template <typename T>
|
|
concept has_both_iterators = has_iterator<T> && has_const_iterator<T>;
|
|
|
|
template <typename T>
|
|
concept only_has_iterator = has_iterator<T> && !has_const_iterator<T>;
|
|
|
|
template <typename T>
|
|
concept has_no_iterators = !has_iterator<T> && !has_const_iterator<T>;
|
|
|
|
template <typename T>
|
|
constexpr bool test_range_concept() {
|
|
return std::ranges::range<std::optional<T>>;
|
|
}
|
|
|
|
template <typename T, std::remove_reference_t<T> __val>
|
|
constexpr bool test() {
|
|
std::remove_reference_t<T> v{__val};
|
|
std::optional<T> opt{v};
|
|
{
|
|
assert(test_range_concept<T>());
|
|
}
|
|
|
|
{ // Dereferencing an iterator of an engaged optional will return the same value that the optional holds.
|
|
auto it = opt.begin();
|
|
auto it2 = std::as_const(opt).begin();
|
|
assert(*it == *opt);
|
|
assert(*it2 == *std::as_const(opt));
|
|
}
|
|
|
|
{ // optional::iterator and optional::const_iterator satisfy the Cpp17RandomAccessIterator and contiguous iterator.
|
|
auto it = opt.begin();
|
|
auto it2 = std::as_const(opt).begin();
|
|
assert(std::contiguous_iterator<decltype(it)>);
|
|
assert(std::contiguous_iterator<decltype(it2)>);
|
|
|
|
assert(std::random_access_iterator<decltype(it)>);
|
|
assert(std::random_access_iterator<decltype(it2)>);
|
|
}
|
|
|
|
{ // const_iterator::value_type == std::remove_cvref_t<T>, const_iterator::reference == const T&, iterator::value_type = std::remove_cvref_t<T>, iterator::reference == T&
|
|
// std::remove_cv_t is impossible for optional<T&>
|
|
auto it = opt.begin();
|
|
auto it2 = std::as_const(opt).begin();
|
|
assert((std::is_same_v<typename decltype(it)::value_type, std::remove_cvref_t<T>>));
|
|
assert((std::is_same_v<typename decltype(it)::reference, std::remove_reference_t<T>&>));
|
|
assert((std::is_same_v<typename decltype(it2)::value_type, std::remove_cvref_t<T>>));
|
|
|
|
// for optional<T&>, there is no const_iterator
|
|
if (!std::is_lvalue_reference_v<T>) {
|
|
assert((std::is_same_v<typename decltype(it2)::reference, const std::remove_reference_t<T>&>));
|
|
}
|
|
}
|
|
|
|
{ // std::ranges::size for an engaged optional<T> == 1, disengaged optional<T> == 0
|
|
const std::optional<T> disengaged{std::nullopt};
|
|
std::optional<T> disengaged2{std::nullopt};
|
|
assert(std::ranges::size(opt) == 1);
|
|
assert(std::ranges::size(std::as_const(opt)) == 1);
|
|
|
|
assert(std::ranges::size(disengaged) == 0);
|
|
assert(std::ranges::size(disengaged2) == 0);
|
|
}
|
|
|
|
{ // std::ranges::enable_view<optional<T>> == true, and std::format_kind<optional<T>> == true
|
|
static_assert(std::ranges::enable_view<std::optional<T>> == true);
|
|
static_assert(std::format_kind<std::optional<T>> == std::range_format::disabled);
|
|
}
|
|
|
|
// 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.
|
|
{
|
|
std::optional<T> val{v};
|
|
assert(val.begin() != val.end());
|
|
val.reset();
|
|
assert(val.begin() == val.end());
|
|
val.emplace(v);
|
|
assert(val.begin() != val.end());
|
|
assert(*(val.begin()) == v);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
constexpr bool tests() {
|
|
// Verify that iterator and const_iterator are present for object type T, but for T&,
|
|
// that only iterator is available iff T is an object type and is not an unbounded array.
|
|
|
|
static_assert(has_both_iterators<std::optional<int>>);
|
|
static_assert(has_both_iterators<std::optional<const int>>);
|
|
static_assert(only_has_iterator<std::optional<int&>>);
|
|
static_assert(only_has_iterator<std::optional<const int&>>);
|
|
static_assert(only_has_iterator<std::optional<int (&)[1]>>);
|
|
static_assert(has_no_iterators<std::optional<int (&)[]>>);
|
|
static_assert(has_no_iterators<std::optional<int (&)()>>);
|
|
|
|
assert((test<int, 1>()));
|
|
assert((test<char, 'a'>()));
|
|
assert((test<bool, true>()));
|
|
assert((test<const int, 2>()));
|
|
assert((test<const char, 'b'>()));
|
|
assert((test<int&, 1>()));
|
|
assert((test<char&, 'a'>()));
|
|
assert((test<bool&, true>()));
|
|
assert((test<const int&, 2>()));
|
|
assert((test<const char&, 'b'>()));
|
|
|
|
assert(!test_range_concept<int (&)()>());
|
|
assert(!test_range_concept<int (&)[]>());
|
|
assert(test_range_concept<int (&)[42]>());
|
|
|
|
return true;
|
|
}
|
|
|
|
int main(int, char**) {
|
|
assert(tests());
|
|
static_assert(tests());
|
|
|
|
return 0;
|
|
}
|