I have been trying to get a better handle on the operator<=>, so I wrote two different string wrappers. The Strong_String
wrapper uses std::strong_ordering
to allow the std::sort
function to sort words like a dictionary sorts words (i.e. a capitalized word occurs before the lowercase word, but not before all lowercase words). The Weak_String
wrapper uses std::weak_ordering
to allow the std::sort
function to sort words in a case insensitive manner.
However, the current version distinguishes between two words being equal (i.e. the same character order and same cases) vs. being equivalent (i.e. the same character order, but with different cases). Is there a use case for this behavior? or would simple case insensitivity be preferred while maintaining the capitalization pattern of the string?
Additionally, suggestions for code improvement are always welcome.
#include <compare>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cassert>
class Strong_String
{
std::string s;
public:
Strong_String(const char* cstr) :s{ cstr } {}
Strong_String(std::string& s_) : s{s_} {}
Strong_String(std::string&& s_) noexcept : s{std::move(s_)} {}
bool operator==(const Strong_String& rhs) noexcept;
std::strong_ordering operator<=>(const Strong_String& rhs) const;
friend std::ostream& operator<<(std::ostream& out, const Strong_String& s);
};
bool Strong_String::operator==(const Strong_String& rhs) noexcept {
return s == rhs.s;
}
std::strong_ordering Strong_String::operator<=>(const Strong_String& rhs) const {
if (s == rhs.s) return std::strong_ordering::equal;
if (s.size() <= rhs.s.size() && std::equal(s.cbegin(), s.cend(),
rhs.s.cbegin(), [](const char c, const char d) {
return std::tolower(c) == std::tolower(d);
}))
{
if (s.size() < rhs.s.size()) return std::strong_ordering::less;
const auto cmp = strcmp(s.c_str(), rhs.s.c_str());
if (cmp < 0) return std::strong_ordering::less;
return std::strong_ordering::greater;
}
if (s.size() > rhs.s.size() && std::equal(rhs.s.cbegin(), rhs.s.cend(),
s.cbegin(), [](const char c, const char d) {
return std::tolower(c) == std::tolower(d);
}))
{
return std::strong_ordering::greater;
}
std::string t, rhs_t;
std::transform(s.begin(), s.end(), std::back_inserter(t),
[](unsigned char c) -> unsigned char {
return tolower(c);
});
std::transform(rhs.s.begin(), rhs.s.end(), std::back_inserter(rhs_t),
[](unsigned char c) -> unsigned char {
return tolower(c);
});
const auto cmp = strcmp(t.c_str(), rhs_t.c_str());
if (cmp < 0) return std::strong_ordering::less;
return std::strong_ordering::greater;
}
std::ostream& operator<<(std::ostream& out, const Strong_String& str) {
out << str.s;
return out;
}
class Weak_String
{
std::string s;
public:
Weak_String(const char* cstr) :s{ cstr } {}
Weak_String(std::string& s_) : s{ s_ } {}
Weak_String(std::string&& s_) noexcept : s{ std::move(s_) } {}
bool operator==(const Weak_String& rhs) noexcept;
std::weak_ordering operator<=>(const Weak_String& rhs) const;
friend std::ostream& operator<<(std::ostream& out, const Weak_String& s);
};
bool Weak_String::operator==(const Weak_String& rhs) noexcept {
return this->s == rhs.s; // Distinguish between equal and equivalent
// return (*this <=> rhs) == 0; // Just Case Insensitive
}
std::weak_ordering Weak_String::operator<=>(const Weak_String& rhs) const
{
if (s == rhs.s) return std::weak_ordering::equivalent;
if (s.size() <= rhs.s.size() && std::equal(s.cbegin(), s.cend(),
rhs.s.cbegin(), [](const char c, const char d) {
return std::tolower(c) == std::tolower(d);
})) // Are the first characters equivalent
{
if (s.size() < rhs.s.size()) return std::weak_ordering::less;
return std::weak_ordering::equivalent;
}
if (s.size() > rhs.s.size() && std::equal(rhs.s.cbegin(), rhs.s.cend(),
s.cbegin(), [](const char c, const char d) {
return std::tolower(c) == std::tolower(d);
})) // Are the first characters equivalent
{
return std::weak_ordering::greater;
}
std::string t, rhs_t;
std::transform(s.begin(), s.end(), std::back_inserter(t),
[](unsigned char c) -> unsigned char {
return tolower(c);
});
std::transform(rhs.s.begin(), rhs.s.end(), std::back_inserter(rhs_t),
[](unsigned char c) -> unsigned char {
return tolower(c);
});
const auto cmp = strcmp(t.c_str(), rhs_t.c_str());
if (cmp < 0) return std::weak_ordering::less;
return std::weak_ordering::greater;
}
std::ostream& operator<<(std::ostream& out, const Weak_String& str) {
out << str.s;
return out;
}
template <typename T>
void helper(T s, T t) {
if (s == t) {
std::cout << s << " is equal to " << t << "\n";
}
else std::cout << s << " is not equal to " << t << "\n";
if (s < t) {
std::cout << s << " is less than " << t << "\n";
}
else std::cout << s << " is not less than " << t << "\n";
if (s > t) {
std::cout << s << " is greater than " << t << "\n";
}
else std::cout << s << " is not greater than " << t << "\n";
if (s <= t) {
std::cout << s << " is less than or equal to " << t << "\n";
}
else std::cout << s << " is not less than or equal to " << t << "\n";
if ((s <=> t) == 0) {
std::cout << s << " is equivalent to " << t << "\n";
}
else std::cout << s << " is not equivalent to " << t << "\n";
std::cout << "\n";
}
int main()
{
std::cout << "strong_ordering String:\n";
Strong_String ss{ "hello" };
Strong_String st{ "HELLO" };
helper(ss, st);
assert(ss != st && "Not Equal");
assert(!(ss < st) && "Not Less Than");
assert(ss > st && "Greater Than");
assert(!(ss <= st) && "Not Less Than or Equal To");
assert((ss <=> st) != 0 && "Not Equivalent");
std::vector<Strong_String> strong_vector = { "cat","dog","CAT","DOG","fish",
"RAT","FISH","rat","doggy","rAT","raT" };
std::sort(strong_vector.begin(), strong_vector.end());
[[gsl::suppress(26486)]]
{
for (const auto astring : strong_vector) std::cout << astring << "\n";
}
std::cout << "\n";
std::cout << "weak_ordering String:\n";
Weak_String ws{ "hello" };
Weak_String wt{ "HELLO" };
helper(ws, wt);
assert(ws != wt && "Not Equal");
assert(!(ws < wt) && "Not Less Than");
assert(!(ws > wt) && "Not Greater Than");
assert(ws <= wt && "Less Than or Equal To");
assert((ws <=> wt) == 0 && "Equivalent");
std::vector<Weak_String> weak_vector = { "cat","DOG","CAT","dog","fish",
"RAT","FISH","rat","doggy","rAT","raT" };
std::sort(weak_vector.begin(), weak_vector.end());
[[gsl::suppress(26486)]]
{
for (const auto astring : weak_vector) std::cout << astring << "\n";
}
std::cout << "\n";
}
output: (using visual studio 2019 with the "Preview" c++ compiler option)
strong_ordering String:
hello is not equal to HELLO
hello is not less than HELLO
hello is greater than HELLO
hello is not less than or equal to HELLO
hello is not equivalent to HELLO
CAT
cat
DOG
dog
doggy
FISH
fish
RAT
rAT
raT
rat
weak_ordering String:
hello is not equal to HELLO
hello is not less than HELLO
hello is not greater than HELLO
hello is less than or equal to HELLO
hello is equivalent to HELLO
cat
CAT
DOG
dog
doggy
fish
FISH
RAT
rat
rAT
raT
C:\Users\David\source\repos\junk2\Debug\junk2.exe (process 21200) exited with code 0.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .
~~~