[libc++] [ranges] SFINAE-friendly "write it three times" in views::counted.

Before this patch, the new test's `CountedInvocable<int*, int*>`
would hard-error instead of SFINAEing and cleanly returning false.

Notice that views::counted specifically does NOT work with pipes;
`counted(42)` is ill-formed. This is because `counted`'s first argument
is supposed to be an iterator, not a range.

Also, mark `views::counted(it, n)` as [[nodiscard]], and test that.
(We have a general policy now that range adaptors are consistently
marked [[nodiscard]], so that people don't accidentally think that
they have side effects. This matters mostly for `reverse` and
`transform`, arguably `drop`, and just generally let's be consistent.)

Differential Revision: https://reviews.llvm.org/D115177
This commit is contained in:
Arthur O'Dwyer 2021-12-06 15:39:08 -05:00
parent 7a06a14f62
commit bd0c0e5b8c
4 changed files with 233 additions and 189 deletions

View File

@ -9,6 +9,7 @@
#ifndef _LIBCPP___RANGES_COUNTED_H
#define _LIBCPP___RANGES_COUNTED_H
#include <__concepts/convertible_to.h>
#include <__config>
#include <__iterator/concepts.h>
#include <__iterator/counted_iterator.h>
@ -16,10 +17,7 @@
#include <__iterator/incrementable_traits.h>
#include <__iterator/iterator_traits.h>
#include <__memory/pointer_traits.h>
#include <__ranges/concepts.h>
#include <__ranges/subrange.h>
#include <__utility/decay_copy.h>
#include <__utility/declval.h>
#include <__utility/forward.h>
#include <__utility/move.h>
#include <span>
@ -36,50 +34,39 @@ _LIBCPP_BEGIN_NAMESPACE_STD
namespace ranges::views {
namespace __counted {
template<class _From, class _To>
concept __explicitly_convertible = requires {
_To(_From{});
};
struct __fn {
template<class _Iter, class _Diff>
requires contiguous_iterator<decay_t<_Iter>> &&
__explicitly_convertible<_Diff, iter_difference_t<_Iter>>
template<contiguous_iterator _It>
_LIBCPP_HIDE_FROM_ABI
constexpr auto operator()(_Iter&& __it, _Diff __c) const
noexcept(noexcept(
span(_VSTD::to_address(__it), static_cast<iter_difference_t<_Iter>>(__c))
))
{
return span(_VSTD::to_address(__it), static_cast<iter_difference_t<_Iter>>(__c));
}
static constexpr auto __go(_It __it, iter_difference_t<_It> __count)
noexcept(noexcept(span(_VSTD::to_address(__it), static_cast<size_t>(__count))))
// Deliberately omit return-type SFINAE, because to_address is not SFINAE-friendly
{ return span(_VSTD::to_address(__it), static_cast<size_t>(__count)); }
template<class _Iter, class _Diff>
requires random_access_iterator<decay_t<_Iter>> &&
__explicitly_convertible<_Diff, iter_difference_t<_Iter>>
template<random_access_iterator _It>
_LIBCPP_HIDE_FROM_ABI
constexpr auto operator()(_Iter&& __it, _Diff __c) const
noexcept(
noexcept(__it + static_cast<iter_difference_t<_Iter>>(__c)) &&
noexcept(ranges::subrange(_VSTD::forward<_Iter>(__it), _VSTD::__decay_copy(__it)))
)
{
auto __last = __it + static_cast<iter_difference_t<_Iter>>(__c);
return ranges::subrange(_VSTD::forward<_Iter>(__it), _VSTD::move(__last));
}
static constexpr auto __go(_It __it, iter_difference_t<_It> __count)
noexcept(noexcept(subrange(__it, __it + __count)))
-> decltype( subrange(__it, __it + __count))
{ return subrange(__it, __it + __count); }
template<class _Iter, class _Diff>
requires __explicitly_convertible<_Diff, iter_difference_t<_Iter>>
template<class _It>
_LIBCPP_HIDE_FROM_ABI
constexpr auto operator()(_Iter&& __it, _Diff __c) const
noexcept(noexcept(
ranges::subrange(counted_iterator(_VSTD::forward<_Iter>(__it), __c), default_sentinel)
))
{
return ranges::subrange(counted_iterator(_VSTD::forward<_Iter>(__it), __c), default_sentinel);
}
static constexpr auto __go(_It __it, iter_difference_t<_It> __count)
noexcept(noexcept(subrange(counted_iterator(_VSTD::move(__it), __count), default_sentinel)))
-> decltype( subrange(counted_iterator(_VSTD::move(__it), __count), default_sentinel))
{ return subrange(counted_iterator(_VSTD::move(__it), __count), default_sentinel); }
template<class _It, convertible_to<iter_difference_t<_It>> _Diff>
requires input_or_output_iterator<decay_t<_It>>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI
constexpr auto operator()(_It&& __it, _Diff&& __count) const
noexcept(noexcept(__go(_VSTD::forward<_It>(__it), _VSTD::forward<_Diff>(__count))))
-> decltype( __go(_VSTD::forward<_It>(__it), _VSTD::forward<_Diff>(__count)))
{ return __go(_VSTD::forward<_It>(__it), _VSTD::forward<_Diff>(__count)); }
};
}
} // namespace __counted
inline namespace __cpo {
inline constexpr auto counted = __counted::__fn{};

View File

@ -0,0 +1,21 @@
//===----------------------------------------------------------------------===//
//
// 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: libcpp-has-no-incomplete-ranges
// Test the libc++ extension that std::views::counted is marked as [[nodiscard]].
#include <ranges>
void test() {
int range[] = {1, 2, 3};
std::views::counted(range, 1); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
}

View File

@ -12,191 +12,225 @@
// std::views::counted;
#include <concepts>
#include <ranges>
#include <span>
#include <cassert>
#include <concepts>
#include <cstddef>
#include <memory>
#include <span>
#include <utility>
#include "test_macros.h"
#include "test_iterators.h"
struct Unrelated {};
struct ConvertibleToSize {
constexpr operator std::ptrdiff_t() const { return 8; }
struct RvalueConvertible {
RvalueConvertible(const RvalueConvertible&) = delete;
operator int() &&;
};
struct ImplicitlyConvertible {
operator short();
explicit operator std::ptrdiff_t() = delete;
struct LvalueConvertible {
LvalueConvertible(const LvalueConvertible&) = delete;
operator int() &;
};
template<class Iter, class T>
concept CountedInvocable = requires(Iter& i, T t) { std::views::counted(i, t); };
struct OnlyExplicitlyConvertible {
explicit operator int() const;
};
template<class... Ts>
concept CountedInvocable = requires (Ts&&... ts) {
std::views::counted(std::forward<Ts>(ts)...);
};
constexpr bool test() {
int buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8};
{
static_assert( CountedInvocable<contiguous_iterator<int*>, ConvertibleToSize>);
static_assert(!CountedInvocable<contiguous_iterator<int*>, ImplicitlyConvertible>);
static_assert(!CountedInvocable<contiguous_iterator<int*>, Unrelated>);
static_assert(std::addressof(std::views::counted) == std::addressof(std::ranges::views::counted));
static_assert(std::semiregular<std::remove_const_t<decltype(std::views::counted)>>);
auto copy = std::views::counted;
static_assert(std::semiregular<decltype(copy)>);
static_assert( CountedInvocable<int*, size_t>);
static_assert(!CountedInvocable<int*, LvalueConvertible>);
static_assert( CountedInvocable<int*, LvalueConvertible&>);
static_assert( CountedInvocable<int*, RvalueConvertible>);
static_assert(!CountedInvocable<int*, RvalueConvertible&>);
static_assert(!CountedInvocable<int*, OnlyExplicitlyConvertible>);
static_assert(!CountedInvocable<int*, int*>);
static_assert(!CountedInvocable<int*>);
static_assert(!CountedInvocable<size_t>);
static_assert(!CountedInvocable<>);
}
{
{
contiguous_iterator<int*> iter(buffer);
std::span<int> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.data() == buffer);
auto c1 = std::views::counted(buffer, 3);
auto c2 = std::views::counted(std::as_const(buffer), 3);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::span<int>);
}
{
const contiguous_iterator<int*> iter(buffer);
std::span<int> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.data() == buffer);
ASSERT_SAME_TYPE(decltype(c1), std::span<int>);
ASSERT_SAME_TYPE(decltype(c2), std::span<const int>);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::span<int>);
}
{
contiguous_iterator<const int*> iter(buffer);
std::span<const int> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.data() == buffer);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::span<const int>);
}
{
const contiguous_iterator<const int*> iter(buffer);
std::span<const int> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.data() == buffer);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::span<const int>);
}
assert(c1.data() == buffer && c1.size() == 3);
assert(c2.data() == buffer && c2.size() == 3);
}
{
{
random_access_iterator<int*> iter(buffer);
std::ranges::subrange<random_access_iterator<int*>> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.begin() == iter);
auto it = contiguous_iterator<int*>(buffer);
auto cit = contiguous_iterator<const int*>(buffer);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::ranges::subrange<random_access_iterator<int*>>);
}
{
const random_access_iterator<int*> iter(buffer);
std::ranges::subrange<random_access_iterator<int*>> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.begin() == iter);
auto c1 = std::views::counted(it, 3);
auto c2 = std::views::counted(std::as_const(it), 3);
auto c3 = std::views::counted(std::move(it), 3);
auto c4 = std::views::counted(contiguous_iterator<int*>(buffer), 3);
auto c5 = std::views::counted(cit, 3);
auto c6 = std::views::counted(std::as_const(cit), 3);
auto c7 = std::views::counted(std::move(cit), 3);
auto c8 = std::views::counted(contiguous_iterator<const int*>(buffer), 3);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::ranges::subrange<random_access_iterator<int*>>);
}
{
random_access_iterator<const int*> iter(buffer);
std::ranges::subrange<random_access_iterator<const int*>> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.begin() == iter);
ASSERT_SAME_TYPE(decltype(c1), std::span<int>);
ASSERT_SAME_TYPE(decltype(c2), std::span<int>);
ASSERT_SAME_TYPE(decltype(c3), std::span<int>);
ASSERT_SAME_TYPE(decltype(c4), std::span<int>);
ASSERT_SAME_TYPE(decltype(c5), std::span<const int>);
ASSERT_SAME_TYPE(decltype(c6), std::span<const int>);
ASSERT_SAME_TYPE(decltype(c7), std::span<const int>);
ASSERT_SAME_TYPE(decltype(c8), std::span<const int>);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::ranges::subrange<random_access_iterator<const int*>>);
}
{
const random_access_iterator<const int*> iter(buffer);
std::ranges::subrange<random_access_iterator<const int*>> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.begin() == iter);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)), std::ranges::subrange<random_access_iterator<const int*>>);
}
assert(c1.data() == buffer && c1.size() == 3);
assert(c2.data() == buffer && c2.size() == 3);
assert(c3.data() == buffer && c3.size() == 3);
assert(c4.data() == buffer && c4.size() == 3);
assert(c5.data() == buffer && c5.size() == 3);
assert(c6.data() == buffer && c6.size() == 3);
assert(c7.data() == buffer && c7.size() == 3);
assert(c8.data() == buffer && c8.size() == 3);
}
{
{
bidirectional_iterator<int*> iter(buffer);
std::ranges::subrange<
std::counted_iterator<bidirectional_iterator<int*>>,
std::default_sentinel_t> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.begin() == std::counted_iterator(iter, 8));
auto it = random_access_iterator<int*>(buffer);
auto cit = random_access_iterator<const int*>(buffer);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)),
std::ranges::subrange<
std::counted_iterator<bidirectional_iterator<int*>>,
std::default_sentinel_t>);
}
{
const bidirectional_iterator<int*> iter(buffer);
std::ranges::subrange<
std::counted_iterator<bidirectional_iterator<int*>>,
std::default_sentinel_t> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.begin() == std::counted_iterator(iter, 8));
auto c1 = std::views::counted(it, 3);
auto c2 = std::views::counted(std::as_const(it), 3);
auto c3 = std::views::counted(std::move(it), 3);
auto c4 = std::views::counted(random_access_iterator<int*>(buffer), 3);
auto c5 = std::views::counted(cit, 3);
auto c6 = std::views::counted(std::as_const(cit), 3);
auto c7 = std::views::counted(std::move(cit), 3);
auto c8 = std::views::counted(random_access_iterator<const int*>(buffer), 3);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)),
std::ranges::subrange<
std::counted_iterator<bidirectional_iterator<int*>>,
std::default_sentinel_t>);
}
{
output_iterator<const int*> iter(buffer);
std::ranges::subrange<
std::counted_iterator<output_iterator<const int*>>,
std::default_sentinel_t> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.begin() == std::counted_iterator(iter, 8));
ASSERT_SAME_TYPE(decltype(c1), std::ranges::subrange<random_access_iterator<int*>>);
ASSERT_SAME_TYPE(decltype(c2), std::ranges::subrange<random_access_iterator<int*>>);
ASSERT_SAME_TYPE(decltype(c3), std::ranges::subrange<random_access_iterator<int*>>);
ASSERT_SAME_TYPE(decltype(c4), std::ranges::subrange<random_access_iterator<int*>>);
ASSERT_SAME_TYPE(decltype(c5), std::ranges::subrange<random_access_iterator<const int*>>);
ASSERT_SAME_TYPE(decltype(c6), std::ranges::subrange<random_access_iterator<const int*>>);
ASSERT_SAME_TYPE(decltype(c7), std::ranges::subrange<random_access_iterator<const int*>>);
ASSERT_SAME_TYPE(decltype(c8), std::ranges::subrange<random_access_iterator<const int*>>);
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)),
std::ranges::subrange<
std::counted_iterator<output_iterator<const int*>>,
std::default_sentinel_t>);
}
{
const output_iterator<const int*> iter(buffer);
std::ranges::subrange<
std::counted_iterator<output_iterator<const int*>>,
std::default_sentinel_t> s = std::views::counted(iter, 8);
assert(s.size() == 8);
assert(s.begin() == std::counted_iterator(iter, 8));
ASSERT_SAME_TYPE(decltype(std::views::counted(iter, 8)),
std::ranges::subrange<
std::counted_iterator<output_iterator<const int*>>,
std::default_sentinel_t>);
}
{
cpp20_input_iterator<int*> iter(buffer);
std::ranges::subrange<
std::counted_iterator<cpp20_input_iterator<int*>>,
std::default_sentinel_t> s = std::views::counted(std::move(iter), 8);
assert(s.size() == 8);
assert(s.begin().base().base() == buffer);
ASSERT_SAME_TYPE(decltype(std::views::counted(std::move(iter), 8)),
std::ranges::subrange<
std::counted_iterator<cpp20_input_iterator<int*>>,
std::default_sentinel_t>);
}
{
std::ranges::subrange<
std::counted_iterator<cpp20_input_iterator<int*>>,
std::default_sentinel_t> s = std::views::counted(cpp20_input_iterator<int*>(buffer), 8);
assert(s.size() == 8);
assert(s.begin().base().base() == buffer);
ASSERT_SAME_TYPE(decltype(std::views::counted(cpp20_input_iterator<int*>(buffer), 8)),
std::ranges::subrange<
std::counted_iterator<cpp20_input_iterator<int*>>,
std::default_sentinel_t>);
}
assert(c1.begin() == it && c1.end() == it + 3);
assert(c2.begin() == it && c2.end() == it + 3);
assert(c3.begin() == it && c3.end() == it + 3);
assert(c4.begin() == it && c4.end() == it + 3);
assert(c5.begin() == cit && c5.end() == cit + 3);
assert(c6.begin() == cit && c6.end() == cit + 3);
assert(c7.begin() == cit && c7.end() == cit + 3);
assert(c8.begin() == cit && c8.end() == cit + 3);
}
{
static_assert(std::same_as<decltype(std::views::counted), decltype(std::ranges::views::counted)>);
auto it = bidirectional_iterator<int*>(buffer);
auto cit = bidirectional_iterator<const int*>(buffer);
auto c1 = std::views::counted(it, 3);
auto c2 = std::views::counted(std::as_const(it), 3);
auto c3 = std::views::counted(std::move(it), 3);
auto c4 = std::views::counted(bidirectional_iterator<int*>(buffer), 3);
auto c5 = std::views::counted(cit, 3);
auto c6 = std::views::counted(std::as_const(cit), 3);
auto c7 = std::views::counted(std::move(cit), 3);
auto c8 = std::views::counted(bidirectional_iterator<const int*>(buffer), 3);
using Expected = std::ranges::subrange<std::counted_iterator<decltype(it)>, std::default_sentinel_t>;
using ConstExpected = std::ranges::subrange<std::counted_iterator<decltype(cit)>, std::default_sentinel_t>;
ASSERT_SAME_TYPE(decltype(c1), Expected);
ASSERT_SAME_TYPE(decltype(c2), Expected);
ASSERT_SAME_TYPE(decltype(c3), Expected);
ASSERT_SAME_TYPE(decltype(c4), Expected);
ASSERT_SAME_TYPE(decltype(c5), ConstExpected);
ASSERT_SAME_TYPE(decltype(c6), ConstExpected);
ASSERT_SAME_TYPE(decltype(c7), ConstExpected);
ASSERT_SAME_TYPE(decltype(c8), ConstExpected);
assert(c1.begin().base() == it && c1.size() == 3);
assert(c2.begin().base() == it && c2.size() == 3);
assert(c3.begin().base() == it && c3.size() == 3);
assert(c4.begin().base() == it && c4.size() == 3);
assert(c5.begin().base() == cit && c5.size() == 3);
assert(c6.begin().base() == cit && c6.size() == 3);
assert(c7.begin().base() == cit && c7.size() == 3);
assert(c8.begin().base() == cit && c8.size() == 3);
}
{
auto it = output_iterator<int*>(buffer);
auto c1 = std::views::counted(it, 3);
auto c2 = std::views::counted(std::as_const(it), 3);
auto c3 = std::views::counted(std::move(it), 3);
auto c4 = std::views::counted(output_iterator<int*>(buffer), 3);
using Expected = std::ranges::subrange<std::counted_iterator<decltype(it)>, std::default_sentinel_t>;
ASSERT_SAME_TYPE(decltype(c1), Expected);
ASSERT_SAME_TYPE(decltype(c2), Expected);
ASSERT_SAME_TYPE(decltype(c3), Expected);
ASSERT_SAME_TYPE(decltype(c4), Expected);
assert(base(c1.begin().base()) == buffer && c1.size() == 3);
assert(base(c2.begin().base()) == buffer && c2.size() == 3);
assert(base(c3.begin().base()) == buffer && c3.size() == 3);
assert(base(c4.begin().base()) == buffer && c4.size() == 3);
}
{
auto it = cpp17_input_iterator<int*>(buffer);
auto c1 = std::views::counted(it, 3);
auto c2 = std::views::counted(std::as_const(it), 3);
auto c3 = std::views::counted(std::move(it), 3);
auto c4 = std::views::counted(cpp17_input_iterator<int*>(buffer), 3);
using Expected = std::ranges::subrange<std::counted_iterator<decltype(it)>, std::default_sentinel_t>;
ASSERT_SAME_TYPE(decltype(c1), Expected);
ASSERT_SAME_TYPE(decltype(c2), Expected);
ASSERT_SAME_TYPE(decltype(c3), Expected);
ASSERT_SAME_TYPE(decltype(c4), Expected);
assert(base(c1.begin().base()) == buffer && c1.size() == 3);
assert(base(c2.begin().base()) == buffer && c2.size() == 3);
assert(base(c3.begin().base()) == buffer && c3.size() == 3);
assert(base(c4.begin().base()) == buffer && c4.size() == 3);
}
{
auto it = cpp20_input_iterator<int*>(buffer);
static_assert(!std::copyable<cpp20_input_iterator<int*>>);
static_assert(!CountedInvocable<cpp20_input_iterator<int*>&, int>);
static_assert(!CountedInvocable<const cpp20_input_iterator<int*>&, int>);
auto c3 = std::views::counted(std::move(it), 3);
auto c4 = std::views::counted(cpp20_input_iterator<int*>(buffer), 3);
using Expected = std::ranges::subrange<std::counted_iterator<decltype(it)>, std::default_sentinel_t>;
ASSERT_SAME_TYPE(decltype(c3), Expected);
ASSERT_SAME_TYPE(decltype(c4), Expected);
assert(base(c3.begin().base()) == buffer && c3.size() == 3);
assert(base(c4.begin().base()) == buffer && c4.size() == 3);
}
return true;

View File

@ -657,6 +657,8 @@ struct cpp20_input_iterator {
constexpr I base() && { return std::move(base_); }
friend constexpr I base(const cpp20_input_iterator& i) { return i.base_; }
template <class T>
void operator,(T const &) = delete;