mimic++ v5
Loading...
Searching...
No Matches
string

Contains symbols for generic string processing. More...

Collaboration diagram for string:

Topics

 is_character
 Type-trait, which determines, whether the given type is a character-type.
 
 string_case_fold_converter
 Type-trait, which provides the case-folding algorithm for the char-type, they are specialized for.
 
 string_literal_prefix
 Yields the printable prefix for any char-type.
 
 string_traits
 Type-trait, which contains properties for the provided string type.
 

Concepts

concept  mimicpp::string
 Determines, whether the given type can be used as a string-type.
 
concept  mimicpp::case_foldable_string
 Determines, whether the given type supports string normalization.
 

Detailed Description

Contains symbols for generic string processing.

There are plenty of different character-types and even more string-types out there. They mostly differ in their details, but in general some common operations shall be applied (like equality- or prefix-tests). Therefore, mimic++ offers various abstractions, which users can utilize to make their char- and string-types compatible with the framework, and thus with all string-matchers.

Custom Strings with common char-type

Custom strings, which do use any of the common char-types (char, wchar_t, char16_t, etc.), can be made compatible in no time. Users do just have to provide a specialization for the string_traits template: Given the following string-type, which in fact is just an abstraction around a std::string:

class MyString
{
public:
explicit MyString(std::string str) noexcept
: m_Inner{std::move(str)}
{
}
std::string_view view() const noexcept
{
return std::string_view{m_Inner};
}
private:
std::string m_Inner{};
};

To make it compatible with mimic++, just that simple specialization is necessary:

template <>
struct mimicpp::string_traits<MyString>
{
// must be the underlying char type
using char_t = char;
// must be a std::ranges::view like type and must be contiguous
using view_t = std::string_view;
// must construct a view object
[[nodiscard]]
static view_t view(const MyString& str) noexcept
{
return str.view();
}
};
Note
These types of strings are already printable by default, if the underlying char-type is formattable.
STATIC_REQUIRE(mimicpp::string<MyString>);
namespace expect = mimicpp::expect;
namespace matches = mimicpp::matches;
using matches::_;
mimicpp::Mock<void(MyString)> mock{};
SCOPED_EXP mock.expect_call("Hello, World!");
SCOPED_EXP mock.expect_call(matches::str::starts_with("Hi", mimicpp::case_insensitive));
mock(MyString{"Hello, World!"}); // matches the first expectation
mock(MyString{"hI, mimic++"}); // matches the second expectation (case-insensitive).

Custom char-types and related strings

It is possible to make custom char-types (and strings which use those) compatible with mimic++, but this requires quite some effort, depending on the features you are planning to use.

Given the following (regular) char-type

struct my_char
{
char c{};
bool operator==(const my_char&) const = default;
};

and this string type:

class ComplexString
{
public:
explicit ComplexString(std::vector<my_char> str) noexcept
: m_Inner{std::move(str)}
{
}
[[nodiscard]]
auto begin() const noexcept
{
return m_Inner.cbegin();
}
[[nodiscard]]
auto end() const noexcept
{
return m_Inner.cend();
}
private:
std::vector<my_char> m_Inner{};
};

At first, we have to tell mimic++, that my_char is an actual char-type. This can be done by specializing the is_character-trait.

template <>
struct mimicpp::is_character<my_char>
: public std::true_type
{
};

Next, the string_traits must be specialized:

template <>
struct mimicpp::string_traits<ComplexString>
{
// must be the underlying char type
using char_t = my_char;
// must be a std::ranges::view like type and must be contiguous
using view_t = std::span<const char_t>;
// must construct a view object
[[nodiscard]]
static view_t view(const ComplexString& str) noexcept
{
return std::span{
str.begin(),
str.end()};
}
};

After that, the string can be used with any string matcher, but just with case-sensitive matching:

mimicpp::Mock<void(ComplexString)> mock{};
ComplexString s{
{{'A'}, {'B'}, {'C'}}
};
SCOPED_EXP mock.expect_call(s);
mock(s);

Support for printing

The current setup is already enough, that these strings can be printed. Unfortunately this will print the whole string just as comma separated hex-values, because mimic++ doesn't know yet, how to print the underlying char-type. This can easily be fixed by proving a custom::Printer specialization for that particular char-type.

Note
Proving a custom printer for the char-type is enough, to make any related string type printable, too.
mimic++ prints all strings with a literal prefix. This depends on the underlying char-type and can be customized by adding a specialization to string_literal_prefix for any user char-type.

Support for case-insensitive matching

As stated in the string-matcher section, case-insensitive-matching requires some sort of case-folding. For ascii-like strings a common strategy is to simply convert all characters to upper-case before the actual comparison. For wide-chars and Unicode this is definitely more complex, if it shall be done correctly. Nevertheless, users can provide their own strategy how to perform the case-folding by simply specializing the string_case_fold_converter trait. For the my_char from above, this may look like follows:

template <>
{
// the string_case_fold_converter must expect the string's view-type and should return
// a forward-range with the underlying char-type.
[[nodiscard]]
auto operator()(string_view_t<ComplexString> view) const
{
return view
| std::views::transform(
[](const my_char c) {
// see notes of https://en.cppreference.com/w/cpp/string/byte/toupper
return my_char{
static_cast<char>(
static_cast<unsigned char>(std::toupper(c.c)))};
});
}
};

This is then enough to make all strings with underlying type of my_char compatible with case-insensitive string-matchers:

namespace matches = mimicpp::matches;
mimicpp::Mock<void(ComplexString)> mock{};
SCOPED_EXP mock.expect_call(matches::str::starts_with(ComplexString{
{{'A'}, {'B'}, {'C'}}
},
mock(ComplexString{
{{'a'}, {'B'}, {'c'}}
});