Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First draft of cetl::pf17::string_view #142

Merged
merged 4 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
323 changes: 323 additions & 0 deletions cetlvast/suites/unittest/test_pf17_string_view.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
/// @file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's critical that we start with type-parameterized tests that compare the CETL implementation to the ones in our representative standard libraries like https://github.com/OpenCyphal/CETL/blob/main/cetlvast/suites/unittest/test_pf20_span.cpp does for span.

Copy link
Contributor Author

@serges147 serges147 Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. See below my response, namely:

My thinking was that it will add more work while we most probably will use char specialization only.

Could you please elaborate why it is critical to invest into what we most probably (plz correct me if I'm wrong) will not use? Are we making CETL universal for the whole world? Honestly, it's quite exhausting to "fight" against c++14 - I believe libcyphal were long time done already if it was not support of 14 standard. And Nunavut's c++ code generation goals will be much simpler and more clean. I do understand that anyway there will be always a "race" against the latest standard and what we "have to support", so polyfills will probably still live in some form... it just concerns me (a lot!) that a lot of simple things become not so simple, and hardly contribute to the final product we deliver to customers. Sorry, for my frustration and being emotional ;)

  1. BTW, you said

... compare the CETL implementation to the ones in our representative standard libraries ..."

I didn't quite catch what you meant by "compare"... I don't see there any comparison of what cetl::pf20::span and std::span does... if this is what you meant.

Anyway, if you confirm (and hopefully explain why) that basic_string_view is a must then of course I will change my tests to be type parameterized. TBD: all of the below?

Defined in header [<string_view>](https://en.cppreference.com/w/cpp/header/string_view)
Type	Definition
std::string_view (C++17)	std::basic_string_view<char>
std::wstring_view (C++17)	std::basic_string_view<wchar_t>
// std::u8string_view (C++20)	std::basic_string_view<char8_t>   // TBD: should be under our pf20?
std::u16string_view (C++17)	std::basic_string_view<char16_t>
std::u32string_view (C++17)	std::basic_string_view<char32_t>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simple answer is that CETL's polyfill types are re-implementations from the standard and the standard defines basic_stringview then provides aliases. We can tolerate sparse implementations but must minimize deviant implementations.

As for the reasoning for C++14? It just goes to the state of the world and compatibility. The need for C++14 polyfill types will naturally diminish over time just as all software gets outdated and deprecated as the years roll by.

/// Unit tests for string_view.hpp
///
/// @copyright
/// Copyright (C) OpenCyphal Development Team <opencyphal.org>
/// Copyright Amazon.com Inc. or its affiliates.
/// SPDX-License-Identifier: MIT
///

#include <cetl/pf17/string_view.hpp>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <string>

using testing::Gt;
using testing::Lt;
using testing::IsNull;

class TestStringView : public testing::Test
{
protected:
};

/// TESTS -----------------------------------------------------------------------------------------------------------

using cetl::pf17::string_view;

TEST_F(TestStringView, DefaultConstructor)
{
const string_view sv;
EXPECT_THAT(sv.data(), IsNull());
EXPECT_THAT(sv.size(), 0u);
EXPECT_TRUE(sv.empty());
}

TEST_F(TestStringView, ConstructFromCString)
{
const char* const cstr = "Hello, World!";
const string_view sv(cstr);
EXPECT_THAT(sv.data(), cstr);
EXPECT_THAT(sv.size(), std::strlen(cstr));
EXPECT_THAT(sv, "Hello, World!");
}

TEST_F(TestStringView, ConstructFromCStringWithLength)
{
const char* cstr = "Hello, World!";
const string_view sv(cstr, 5);
EXPECT_THAT(sv.size(), 5u);
EXPECT_THAT(sv, "Hello");
}

TEST_F(TestStringView, ConstructFromStdString)
{
std::string str = "Hello, World!";
const string_view sv(str);
EXPECT_THAT(sv.data(), str.data());
EXPECT_THAT(sv.size(), str.size());
EXPECT_THAT(sv, str);
}

TEST_F(TestStringView, SizeAndLength)
{
const string_view sv("Test string");
EXPECT_THAT(sv.size(), 11u);
EXPECT_THAT(sv.length(), 11u);
EXPECT_THAT(sv.max_size(),
(string_view::npos - sizeof(string_view::size_type) - sizeof(void*)) / sizeof(string_view::value_type) /
4);
}

TEST_F(TestStringView, Empty)
{
const string_view sv1;
EXPECT_TRUE(sv1.empty());

const string_view sv2("");
EXPECT_TRUE(sv2.empty());

const string_view sv3("Non-empty");
EXPECT_FALSE(sv3.empty());
}

TEST_F(TestStringView, ElementAccessOperator)
{
const string_view sv("abcdef");
EXPECT_THAT(sv[0], 'a');
EXPECT_THAT(sv[1], 'b');
EXPECT_THAT(sv[5], 'f');
}

TEST_F(TestStringView, ElementAccessAt)
{
const string_view sv("abcdef");
EXPECT_THAT(sv.at(0), 'a');
EXPECT_THAT(sv.at(5), 'f');
#if defined(__cpp_exceptions)
EXPECT_THROW(sv.at(6), std::out_of_range);
#endif
}

TEST_F(TestStringView, FrontBack)
{
const string_view sv("abcdef");
EXPECT_THAT(sv.front(), 'a');
EXPECT_THAT(sv.back(), 'f');
}

TEST_F(TestStringView, Data)
{
const char* const cstr = "Hello, World!";
const string_view sv(cstr);
EXPECT_THAT(sv.data(), cstr);
}

TEST_F(TestStringView, Iterators)
{
const string_view sv("abcdef");
std::string str;
for (auto it = sv.begin(); it != sv.end(); ++it) // NOLINT
{
str += *it;
}
EXPECT_THAT(str, "abcdef");
}

TEST_F(TestStringView, ConstIterators)
{
const string_view sv("abcdef");
std::string str;
for (auto it = sv.cbegin(); it != sv.cend(); ++it) // NOLINT
{
str += *it;
}
EXPECT_THAT(str, "abcdef");
}

TEST_F(TestStringView, RemovePrefix)
{
string_view sv("abcdef");
sv.remove_prefix(2);
EXPECT_THAT(sv, "cdef");
}

TEST_F(TestStringView, RemoveSuffix)
{
string_view sv("abcdef");
sv.remove_suffix(2);
EXPECT_THAT(sv, "abcd");
}

TEST_F(TestStringView, Swap)
{
string_view sv1("Hello");
string_view sv2("World");
sv1.swap(sv2);
EXPECT_THAT(sv1, "World");
EXPECT_THAT(sv2, "Hello");
}

TEST_F(TestStringView, SwapNonMember)
{
string_view sv1("Hello");
string_view sv2("World");
swap(sv1, sv2);
EXPECT_THAT(sv1, "World");
EXPECT_THAT(sv2, "Hello");
}

TEST_F(TestStringView, Copy)
{
const string_view sv("Hello, World!");
char buffer[20] = {};
const size_t copied = sv.copy(buffer, 5);
EXPECT_THAT(copied, 5u);
EXPECT_THAT(std::string(buffer, copied), "Hello");
}

TEST_F(TestStringView, Substr)
{
const string_view sv("Hello, World!");
const string_view sub = sv.substr(7, 5);
EXPECT_THAT(sub, "World");
#if defined(__cpp_exceptions)
EXPECT_THROW(sv.substr(20), std::out_of_range);
#endif
}

TEST_F(TestStringView, Compare)
{
const string_view sv1("abc");
const string_view sv2("abc");
const string_view sv3("abd");
const string_view sv4("abcd");

EXPECT_THAT(sv1.compare(sv2), 0);
EXPECT_THAT(sv1.compare(sv3), Lt(0));
EXPECT_THAT(sv3.compare(sv1), Gt(0));
EXPECT_THAT(sv1.compare(sv4), Lt(0));
EXPECT_THAT(sv4.compare(sv1), Gt(0));
}

TEST_F(TestStringView, FindChar)
{
const string_view sv("Hello, World!");
EXPECT_THAT(sv.find('W'), 7u);
EXPECT_THAT(sv.find('z'), string_view::npos);
}

TEST_F(TestStringView, FindStringView)
{
const string_view sv("Hello, World!");
const string_view to_find("World");
EXPECT_THAT(sv.find(to_find), 7u);
EXPECT_THAT(sv.find(""), 0u);
EXPECT_THAT(sv.find("Earth"), string_view::npos);
EXPECT_THAT(sv.find("too long too long too long"), string_view::npos);
}

TEST_F(TestStringView, StartsWith)
{
const string_view sv("Hello, World!");
EXPECT_TRUE(sv.starts_with("Hello"));
EXPECT_TRUE(sv.starts_with(""));
EXPECT_FALSE(sv.starts_with("World"));
EXPECT_FALSE(sv.starts_with("too long too long too long"));
}

TEST_F(TestStringView, EndsWith)
{
const string_view sv("Hello, World!");
EXPECT_TRUE(sv.ends_with("World!"));
EXPECT_TRUE(sv.ends_with(""));
EXPECT_FALSE(sv.ends_with("Hello"));
EXPECT_FALSE(sv.ends_with("too long too long too long"));
}

TEST_F(TestStringView, RelationalOperators)
{
const string_view sv1("abc");
const string_view sv2("abc");
const string_view sv3("abd");

EXPECT_TRUE(sv1 == sv2);
EXPECT_FALSE(sv1 != sv2);
EXPECT_TRUE(sv1 < sv3);
EXPECT_TRUE(sv3 > sv1);
EXPECT_TRUE(sv1 <= sv2);
EXPECT_TRUE(sv1 >= sv2);
}

TEST_F(TestStringView, FindOutOfBounds)
{
const string_view sv("Hello");
EXPECT_THAT(sv.find('H', 10), string_view::npos);
EXPECT_THAT(sv.find("He", 10), string_view::npos);
}

TEST_F(TestStringView, RemovePrefixOutOfBounds)
{
string_view sv("Hello");
sv.remove_prefix(10);
EXPECT_THAT(sv.size(), 0u);
}

TEST_F(TestStringView, RemoveSuffixOutOfBounds)
{
string_view sv("Hello");
sv.remove_suffix(10);
EXPECT_THAT(sv.size(), 0u);
}

TEST_F(TestStringView, SubstrWithNpos)
{
const string_view sv("Hello, World!");
const string_view sub = sv.substr(7);
EXPECT_THAT(sub, "World!");
}

TEST_F(TestStringView, EmptyStringViewOperations)
{
const string_view sv;
EXPECT_THAT(sv.size(), 0u);
EXPECT_TRUE(sv.empty());
EXPECT_THAT(sv.data(), IsNull());
EXPECT_THAT(sv.begin(), sv.end());
}

TEST_F(TestStringView, ComparisonWithString)
{
const string_view sv("Hello");
const std::string str = "Hello";
EXPECT_THAT(sv, str);
EXPECT_THAT(str, sv);
}

TEST_F(TestStringView, ComparisonWithCString)
{
const string_view sv("Hello");
const char* const cstr = "Hello";
EXPECT_THAT(sv, cstr);
EXPECT_THAT(cstr, sv);
}

TEST_F(TestStringView, FindPartial)
{
const string_view sv("ababab");
EXPECT_THAT(sv.find("aba"), 0u);
EXPECT_THAT(sv.find("aba", 1), 2u);
}

TEST_F(TestStringView, CopyOutOfBounds)
{
#if defined(__cpp_exceptions)
const string_view sv("Hello");
char buffer[10];
EXPECT_THROW(sv.copy(buffer, 5, 6), std::out_of_range);
#else
GTEST_SKIP() << "Not applicable when exceptions are disabled.";
#endif
}
8 changes: 8 additions & 0 deletions include/cetl/pf17/cetlpf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
# include <cstddef>
# include <optional>
# include <variant>
# include <string_view>

namespace cetl
{
Expand Down Expand Up @@ -113,13 +114,17 @@ using std::get_if;
using std::visit;
using std::holds_alternative;

// string_view
using std::string_view;

} // namespace cetl

#else
# include "cetl/pf17/byte.hpp"
# include "cetl/pf17/utility.hpp"
# include "cetl/pf17/optional.hpp"
# include "cetl/pf17/variant.hpp"
# include "cetl/pf17/string_view.hpp"

namespace cetl
{
Expand Down Expand Up @@ -196,6 +201,9 @@ using cetl::pf17::holds_alternative;
using cetl::pf17::bad_variant_access;
# endif

// string_view
using cetl::pf17::string_view;

} // namespace cetl
#endif

Expand Down
Loading
Loading