Skip to content

Commit 8d7fb78

Browse files
committed
feat utils: migrate utils::span to C++20
commit_hash:b5471b5a0464d46c444f56c171f59ea7621050f6
1 parent 0f3bb76 commit 8d7fb78

5 files changed

Lines changed: 92 additions & 132 deletions

File tree

core/src/websocket/connection.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class WebSocketConnectionImpl final : public WebSocketConnection {
140140
need_data_masking_
141141
);
142142

143-
SendFrame(*io_, frame, fragment, need_data_masking_);
143+
SendFrame(*io_, {frame.data(), frame.size()}, fragment, need_data_masking_);
144144
continuation = impl::frames::Continuation::kYes;
145145
data_to_send = data_to_send.last(data_to_send.size() - config_.fragment_size);
146146
}
@@ -153,7 +153,7 @@ class WebSocketConnectionImpl final : public WebSocketConnection {
153153
impl::frames::Final::kYes,
154154
need_data_masking_
155155
);
156-
SendFrame(*io_, frame, data_to_send, need_data_masking_);
156+
SendFrame(*io_, {frame.data(), frame.size()}, data_to_send, need_data_masking_);
157157
}
158158
}
159159

kafka/src/kafka/impl/consumer_impl.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -536,10 +536,9 @@ std::vector<std::uint32_t> ConsumerImpl::GetPartitionIds(
536536

537537
const utils::span<const rd_kafka_metadata_topic>
538538
topics{metadata->topics, static_cast<std::size_t>(metadata->topic_cnt)};
539-
const auto*
540-
topic_it = std::find_if(topics.begin(), topics.end(), [&topic](const rd_kafka_metadata_topic& topic_raw) {
541-
return topic == topic_raw.topic;
542-
});
539+
const auto topic_it = std::ranges::find_if(topics, [&topic](const rd_kafka_metadata_topic& topic_raw) {
540+
return topic == topic_raw.topic;
541+
});
543542
if (topic_it == topics.end()) {
544543
throw TopicNotFoundException{fmt::format("Failed to find topic: {}", topic)};
545544
}

kafka/tests/producer_kafkatest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ UTEST_F(ProducerTest, OneProducerHeadersSendAsyncMemorySafety) {
115115
"test-key",
116116
"test-msg",
117117
/*partition=*/kafka::kUnassignedPartition,
118-
headers_small_vector
118+
{headers_small_vector.data(), headers_small_vector.size()}
119119
);
120120
}
121121
UEXPECT_NO_THROW(task_vector.Get());

universal/include/userver/utils/span.hpp

Lines changed: 36 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -3,158 +3,69 @@
33
/// @file userver/utils/span.hpp
44
/// @brief @copybrief utils::span
55

6+
#include <concepts>
67
#include <cstddef>
78
#include <iterator>
9+
#include <span>
810
#include <type_traits>
911

12+
// TODO remove extra include
1013
#include <userver/utils/assert.hpp>
1114

1215
USERVER_NAMESPACE_BEGIN
1316

1417
namespace utils {
1518

16-
namespace impl {
17-
19+
/// A polyfill for std::span with some of the newer features enabled.
1820
template <typename T>
19-
struct TypeIdentityImpl final {
20-
using type = T;
21-
};
21+
class span : public std::span<T> { // NOLINT(readability-identifier-naming)
22+
public:
23+
// std::span gains this alias only in C++23.
24+
using const_iterator = typename std::span<T>::iterator;
2225

23-
template <typename T>
24-
using TypeIdentity = typename TypeIdentityImpl<T>::type;
26+
using std::span<T>::span;
2527

26-
} // namespace impl
28+
constexpr explicit(false) span(std::span<T> s) noexcept : std::span<T>(s) {}
2729

28-
/// A polyfill for std::span from C++20
29-
template <typename T>
30-
class span final { // NOLINT(readability-identifier-naming)
31-
public:
32-
using iterator = T*;
33-
using value_type = std::remove_cv_t<T>;
34-
35-
constexpr span() noexcept : span(nullptr, nullptr) {}
36-
37-
constexpr span(T* begin, T* end) noexcept : begin_(begin), end_(end) {
38-
// GCC 11.4.0 has issues with comparing pointers at compile time.
39-
UASSERT(
40-
std::is_constant_evaluated() || (begin != nullptr && end != nullptr && begin <= end) ||
41-
(begin == nullptr && end == nullptr)
42-
);
43-
}
44-
45-
constexpr span(T* begin, std::size_t size) noexcept : begin_(begin), end_(begin + size) {
46-
// GCC 11.4.0 has issues with comparing pointers at compile time.
47-
UASSERT(std::is_constant_evaluated() || begin != nullptr || size == 0);
48-
}
49-
50-
#if defined(__GNUC__) && !defined(__clang__)
51-
#if __GNUC__ >= 9
52-
#pragma GCC diagnostic push
53-
#pragma GCC diagnostic ignored "-Winit-list-lifetime"
54-
#endif
55-
#endif
56-
template <typename Void = void, typename = std::enable_if_t<std::is_const_v<T> && std::is_void_v<Void>>>
57-
constexpr /*implicit*/ span(std::initializer_list<value_type> il)
58-
: begin_(il.begin()),
59-
end_(il.end())
60-
{}
61-
#if defined(__GNUC__) && !defined(__clang__)
62-
#if __GNUC__ >= 9
63-
#pragma GCC diagnostic pop
64-
#endif
65-
#endif
66-
67-
template <
68-
typename Container,
69-
typename = std::enable_if_t<
70-
// Either Container is lvalue, or this span's elements are const
71-
(std::is_lvalue_reference_v<Container> || std::is_const_v<T>)&&
72-
// Copy and move constructor fix
73-
!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Container>>, span> &&
74-
// Container is a range of T
75-
std::is_convertible_v<decltype(std::data(std::declval<Container&>())), T*>>>
76-
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
77-
constexpr /*implicit*/ span(Container&& cont) noexcept : span(std::data(cont), std::size(cont)) {}
78-
79-
template <std::size_t Size>
80-
constexpr /*implicit*/ span(impl::TypeIdentity<T> (&array)[Size]) noexcept
81-
: span(std::data(array), std::size(array)) {}
82-
83-
constexpr T* begin() const noexcept { return begin_; }
84-
constexpr T* end() const noexcept { return end_; }
85-
86-
constexpr T* data() const noexcept { return begin_; }
87-
constexpr std::size_t size() const noexcept { return end_ - begin_; }
88-
constexpr bool empty() const noexcept { return size() == 0; }
89-
90-
constexpr span<T> first(std::size_t count) const noexcept {
91-
UASSERT(count <= size());
92-
return span{begin_, begin_ + count};
93-
}
94-
95-
constexpr span<T> last(std::size_t count) const noexcept {
96-
UASSERT(count <= size());
97-
return span{end_ - count, end_};
98-
}
99-
100-
constexpr span<T> subspan(std::size_t offset) const noexcept {
101-
UASSERT(offset <= size());
102-
return span{begin_ + offset, end_};
103-
}
104-
105-
constexpr span<T> subspan(
106-
std::size_t offset, //
107-
std::size_t count
108-
) const noexcept {
109-
UASSERT(offset + count <= size());
110-
return span{begin_ + offset, begin_ + offset + count};
111-
}
112-
113-
constexpr T& operator[](std::size_t index) const noexcept {
114-
UASSERT(index < size());
115-
return begin_[index];
116-
}
117-
118-
private:
119-
T* begin_;
120-
T* end_;
30+
// Allows converting utils::span<U> to utils::span<T>, following std::span.
31+
template <typename U>
32+
requires std::is_convertible_v<U (*)[], T (*)[]>
33+
constexpr explicit(false) span(span<U> other) noexcept : std::span<T>(other.data(), other.size()) {}
34+
35+
// std::span will only gain this constructor in C++29 or later.
36+
constexpr explicit(false) span(std::initializer_list<std::remove_cv_t<T>> il) noexcept
37+
requires std::is_const_v<T> && (!std::same_as<std::decay_t<T>, bool>)
38+
: std::span<T>(il.begin(), il.end()) {}
12139
};
12240

123-
template <typename Container>
124-
span(Container&& cont) -> span<std::remove_reference_t<decltype(*std::begin(cont))>>;
41+
template <typename It, typename EndOrSize>
42+
span(It, EndOrSize) -> span<std::remove_reference_t<decltype(*std::declval<It&>())>>;
43+
44+
template <typename R>
45+
requires std::ranges::contiguous_range<R>
46+
span(R&&) -> span<std::remove_reference_t<decltype(*std::begin(std::declval<R&>()))>>;
12547

126-
/// A polyfill for std::as_bytes from C++20
48+
/// Reinterprets the elements of a span as bytes.
12749
template <typename T>
12850
span<const std::byte> as_bytes(span<T> s) noexcept { // NOLINT(readability-identifier-naming)
129-
const auto* const data = reinterpret_cast<const std::byte*>(s.data());
130-
return {data, data + s.size() * sizeof(T)};
51+
return span<const std::byte>{std::as_bytes(std::span<T>{s})};
13152
}
13253

133-
/// A polyfill for std::as_writable_bytes from C++20
134-
template <typename T, typename = std::enable_if_t<!std::is_const_v<T>>>
54+
/// Reinterprets the elements of a span as writable bytes.
55+
template <typename T>
56+
requires(!std::is_const_v<T>)
13557
span<std::byte> as_writable_bytes(span<T> s) noexcept { // NOLINT(readability-identifier-naming)
136-
auto* const data = reinterpret_cast<std::byte*>(s.data());
137-
return {data, data + s.size() * sizeof(T)};
58+
return span<std::byte>{std::as_writable_bytes(std::span<T>{s})};
13859
}
13960

14061
} // namespace utils
14162

14263
USERVER_NAMESPACE_END
14364

144-
/// @cond
145-
146-
// Boost requires ranges to have a nested constant_iterator alias,
147-
// but utils::span does not have one.
148-
namespace boost {
149-
150-
template <typename T, typename Enabler>
151-
struct range_const_iterator;
152-
65+
// std::span must implement std::ranges::enable_borrowed_range, so <span> will pull it in.
15366
template <typename T>
154-
struct range_const_iterator<USERVER_NAMESPACE::utils::span<T>, void> {
155-
using type = typename USERVER_NAMESPACE::utils::span<T>::iterator;
156-
};
157-
158-
} // namespace boost
67+
inline constexpr bool std::ranges::enable_borrowed_range<USERVER_NAMESPACE::utils::span<T>> = true;
15968

160-
/// @endcond
69+
// std::span must implement std::ranges::enable_view, so <span> will pull it in.
70+
template <typename T>
71+
inline constexpr bool std::ranges::enable_view<USERVER_NAMESPACE::utils::span<T>> = true;

universal/src/utils/span_test.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <userver/utils/span.hpp>
22

33
#include <numeric>
4+
#include <ranges>
45
#include <vector>
56

67
#include <gmock/gmock.h>
@@ -43,4 +44,53 @@ TEST(UtilsSpan, BoostAdapters) {
4344
EXPECT_THAT(transformed, testing::ElementsAre(2, 4, 6));
4445
}
4546

47+
TEST(UtilsSpan, DangerousInitializerListConversions) {
48+
// https://cplusplus.github.io/LWG/issue4520
49+
bool data[4] = {true, false, true, false};
50+
bool* ptr = data;
51+
std::size_t size = 4;
52+
53+
{
54+
utils::span<const bool> span{ptr, size};
55+
EXPECT_THAT(span, testing::ElementsAre(true, false, true, false));
56+
}
57+
{
58+
utils::span span{ptr, size};
59+
static_assert(std::same_as<std::ranges::range_reference_t<decltype(span)>, bool&>);
60+
EXPECT_THAT(span, testing::ElementsAre(true, false, true, false));
61+
}
62+
}
63+
64+
TEST(UtilsSpan, DangerousInitializerListConversionsInt) {
65+
// https://cplusplus.github.io/LWG/issue4520
66+
int data[4] = {1, 2, 3, 4};
67+
int* ptr = data;
68+
std::size_t size = 4;
69+
70+
{
71+
utils::span<const int> span{ptr, size};
72+
EXPECT_THAT(span, testing::ElementsAre(1, 2, 3, 4));
73+
}
74+
{
75+
utils::span span{ptr, size};
76+
static_assert(std::same_as<std::ranges::range_reference_t<decltype(span)>, int&>);
77+
EXPECT_THAT(span, testing::ElementsAre(1, 2, 3, 4));
78+
}
79+
}
80+
81+
TEST(UtilsSpan, DangerousInitializerListCTAD) {
82+
std::vector<int> array{1, 2, 3};
83+
84+
{
85+
utils::span span{array};
86+
static_assert(std::same_as<std::ranges::range_reference_t<decltype(span)>, int&>);
87+
EXPECT_THAT(span, testing::ElementsAre(1, 2, 3));
88+
}
89+
{
90+
utils::span span{std::as_const(array)};
91+
static_assert(std::same_as<std::ranges::range_reference_t<decltype(span)>, const int&>);
92+
EXPECT_THAT(span, testing::ElementsAre(1, 2, 3));
93+
}
94+
}
95+
4696
USERVER_NAMESPACE_END

0 commit comments

Comments
 (0)