pw_span: Fix deduction guides for containers
Previously, the constness of the container implied the constness of the
elements, which is not always correct. A mutable std::string has mutable
elements, but a mutable std::string_view has const elements. The result
was that template argument deduction failed in some cases, including a
mutable std::string_view.
This change has the container deduction guides use the actual element
type from the container as the element type for the span.
Change-Id: Idafde58b8cfedf86e1f90a3e22fb05780903222e
diff --git a/pw_span/public/pw_span/span.h b/pw_span/public/pw_span/span.h
index 4f85eb1..2ea01ff 100644
--- a/pw_span/public/pw_span/span.h
+++ b/pw_span/public/pw_span/span.h
@@ -465,11 +465,22 @@
template <class T, std::size_t N>
span(const std::array<T, N>&) -> span<const T, N>;
+namespace internal {
+
+// Containers can be mutable or const and have mutable or const members. Check
+// the type of the accessed elements to determine which type of span should be
+// created (e.g. span<char> or span<const char>).
+template <typename T>
+using ValueType = std::remove_reference_t<decltype(std::declval<T>()[0])>;
+
+} // namespace internal
+
+// This diverges a little from the standard, which uses std::ranges.
template <class Container>
-span(Container&) -> span<typename Container::value_type>;
+span(Container&) -> span<internal::ValueType<Container>>;
template <class Container>
-span(const Container&) -> span<const typename Container::value_type>;
+span(const Container&) -> span<internal::ValueType<const Container>>;
#endif // __cpp_deduction_guides
diff --git a/pw_span/span_test.cc b/pw_span/span_test.cc
index f4770f6..37875f6 100644
--- a/pw_span/span_test.cc
+++ b/pw_span/span_test.cc
@@ -58,7 +58,6 @@
} // namespace
-// Pigweed: Test deducing from std::string_view.
TEST(SpanTest, DeductionGuides_MutableArray) {
char array[] = {'a', 'b', 'c', 'd', '\0'};
@@ -101,27 +100,112 @@
EXPECT_STREQ(the_span.data(), "abcd");
}
-TEST(SpanTest, DeductionGuides_MutableContainer) {
- std::vector<int> foo = {3456};
-
- auto the_span = span(foo);
- static_assert(the_span.extent == dynamic_extent);
-
- EXPECT_EQ(foo[0], the_span[0]);
- EXPECT_EQ(foo.size(), the_span.size());
-
- the_span[0] = 9876;
- EXPECT_EQ(9876, foo[0]);
-}
-
-TEST(SpanTest, DeductionGuides_ConstContainer) {
- auto the_span = span(std::string_view("Hello"));
+TEST(SpanTest, DeductionGuides_MutableContainerWithConstElements) {
+ std::string_view string("Hello");
+ auto the_span = span(string);
static_assert(the_span.extent == dynamic_extent);
EXPECT_STREQ("Hello", the_span.data());
EXPECT_EQ(5u, the_span.size());
}
+TEST(SpanTest, DeductionGuides_MutableContainerWithMutableElements) {
+ std::string string("Hello");
+ auto the_span = span(string);
+ static_assert(the_span.extent == dynamic_extent);
+
+ EXPECT_EQ(5u, the_span.size());
+ the_span[1] = 'a';
+ EXPECT_STREQ(the_span.data(), string.data());
+ EXPECT_STREQ("Hallo", the_span.data());
+}
+
+class MutableStringView {
+ public:
+ using element_type = char;
+ using value_type = char;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using pointer = char*;
+ using reference = char&;
+ using iterator = char*;
+ using const_iterator = const char*;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ MutableStringView(char* str) : data_(str, std::strlen(str)) {}
+
+ char& operator[](size_type index) const { return data_[index]; }
+ pointer data() const { return data_.data(); }
+ size_type size() const { return data_.size(); }
+ iterator begin() const { return data_.begin(); }
+ iterator end() const { return data_.end(); }
+
+ private:
+ span<char> data_;
+};
+
+TEST(SpanTest, DeductionGuides_ConstContainerWithMutableElements) {
+ char data[] = "54321";
+ MutableStringView view(data);
+
+ auto the_span = span(view);
+ static_assert(the_span.extent == dynamic_extent);
+
+ EXPECT_EQ(5u, the_span.size());
+ view[2] = '?';
+ EXPECT_STREQ("54?21", the_span.data());
+ EXPECT_STREQ("54?21", data);
+}
+
+TEST(SpanTest, DeductionGuides_ConstContainerWithMutableValueType) {
+ const std::string string("Hello");
+ auto the_span = span(string);
+ static_assert(the_span.extent == dynamic_extent);
+
+ EXPECT_EQ(5u, the_span.size());
+ EXPECT_STREQ("Hello", the_span.data());
+}
+
+TEST(SpanTest, DeductionGuides_ConstContainerWithConstElements) {
+ const std::string_view string("Hello");
+ auto the_span = span(string);
+ static_assert(the_span.extent == dynamic_extent);
+
+ EXPECT_EQ(5u, the_span.size());
+ EXPECT_STREQ("Hello", the_span.data());
+}
+
+TEST(SpanTest, DeductionGuides_FromTemporary_ContainerWithConstElements) {
+ auto the_span = span(std::string_view("Hello"));
+ static_assert(the_span.extent == dynamic_extent);
+
+ EXPECT_EQ(5u, the_span.size());
+ EXPECT_STREQ("Hello", the_span.data());
+}
+
+TEST(SpanTest, DeductionGuides_FromReference) {
+ std::array<int, 5> array{1, 3, 5, 7, 9};
+ std::array<int, 5>& array_ref = array;
+
+ auto the_span = span(array_ref);
+ static_assert(the_span.extent == 5);
+
+ for (unsigned i = 0; i < array.size(); ++i) {
+ ASSERT_EQ(array[i], the_span[i]);
+ }
+}
+
+TEST(SpanTest, DeductionGuides_FromConstReference) {
+ std::string_view string = "yo!";
+ const std::string_view& string_ref = string;
+
+ auto the_span = span(string_ref);
+ static_assert(the_span.extent == dynamic_extent);
+
+ EXPECT_EQ(string, the_span.data());
+}
+
TEST(SpanTest, DefaultConstructor) {
span<int> dynamic_span;
EXPECT_EQ(nullptr, dynamic_span.data());