6
\$\begingroup\$

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 . . .
~~~

\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

My main suggestion for this code is for the helper function. It's not a good idea to only compare a few selected strings from the set of all interesting strings. It's far more efficient to just have a list of strings that are already ordered, and then ensure that this ordering is represented by the <=> operator under test.

To do this, each element should be compared to each element, including itself. The code I usually use to do this is:

#include <cassert>
#include <string>
#include <vector>
#include <iostream>

int sign(int i)
{
    return i < 0 ? -1 : i > 0 ? +1 : 0;
}

template<typename T>
int spaceship(T a, T b)
{
    return a < b ? -123 : a > b ? +123 : 0;
}

std::string op(int cmp)
{
    return cmp < 0 ? "<" : cmp > 0 ? ">" : "==";
}

template<typename T>
void test_spaceship(const std::vector<T> &elements)
{
    bool error = false;

    for (std::size_t i = 0; i < elements.size(); ++i) {
        for (std::size_t j = 0; j < elements.size(); ++j) {
            int expected = spaceship(i, j);
            int actual = spaceship(elements[i], elements[j]);
            if (sign(expected) != sign(actual)) {
                std::cerr << __func__ << ":\n";
                std::cerr << "  expected " << elements[i] << " "
                          << op(expected) << " " << elements[j] << "\n";
                std::cerr << "  but got  " << elements[i] << " "
                          << op(actual) << " " << elements[j] << "\n";
                error = true;
            }
        }
    }
    std::flush(std::cerr);
    assert(!error);
}

int main()
{
    std::vector<std::string> elements{
            "",
            "first",
            "second",
            "zfourth", // intentionally in the wrong order
            "third",
            "zzfifth"
    };

    test_spaceship(elements);
    std::cout << "ok\n";
    std::string s;
    std::getline(std::cin, s);
}

Of course you would have to adjust the code a bit to test your Strong_String instead of my std::string, but the general idea should get clear.

If you have some strings that are considered equal by your operator <=>, you would have to adjust the above code to have a std::vector<std::vector<T>>, but that should be equally easy.


My other favorite topic is the cctype header since you must never feed a plain character to functions like isalnum or toupper. Furthermore by doing this, you limit your program to 8-bit character sets, unless CHAR_BIT is greater than 8 on your machine, and chances are small for that.

You should rather treat your strings as Unicode strings, and that brings a whole new topic of decisions, such as sorting strings from different scripts and languages. But that's still better than being caught in the 1990s with their limited code pages.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the systematic test, I'll be sure to use it in my code. Can you tell me why for (const auto astring : weak_vector) std::cout << astring << "\n"; is giving me a "Don't pass a pointer that may be invalid to a function. Parameter 0 '@weak_vector' in call to 'Weak_String::Weak_String' may be invalid (lifetime.3)." warning (C26486)? \$\endgroup\$
    – davidbear
    Commented Nov 29, 2019 at 15:10
  • \$\begingroup\$ No, I cannot. But have a close look at the error message. The keyword lifetime.3 looks like a reference to the C++ standard or a related document, and C26486 has an error number for exactly one reason: to search for it unambiguously. \$\endgroup\$ Commented Nov 29, 2019 at 18:19
2
\$\begingroup\$

After reading Roland Illig's review and altering the code according to his recommendations, I went searching other improvements. The first thing I realized is that the code for the operator<=> function in Strong_String and Weak_String was really inefficient. So, I rewrote the function as follows:

std::strong_ordering Strong_String::operator<=>(const Strong_String& rhs) const
{
    const auto cmp = [](const unsigned char c, const unsigned char d) {
        return std::tolower(c) == std::tolower(d);
    };

    if (s == rhs.s) return std::strong_ordering::equal;
    auto values = std::mismatch(s.cbegin(), s.cend(), 
        rhs.s.cbegin(), rhs.s.cend(), cmp);
    if (values.first == s.cend()) {
        if (values.second == rhs.s.cend()) {    // no mismatches same length
            const auto comp = strcmp(s.c_str(), rhs.s.c_str()); // uppercase first
            if (comp < 0) return std::strong_ordering::less;
            return std::strong_ordering::greater;
        }
        return std::strong_ordering::less;      // s is shorter than rhs.s
    }
    if (values.second == rhs.s.cend()) return std::strong_ordering::greater;
    if (tolower(*(values.first)) < tolower(*(values.second))) {
        return std::strong_ordering::less;
    }
    return std::strong_ordering::greater;
}

In addition to being more compact and easier to read, this has the advantage of not having any unnecessary copying or additional looping, and it gets rid of code redundancy naming the lambda function. I have replaced the std::equal function with the std::mismatch function. This allows me to determine if the strings are the same as well as capture the differential character.

Well, that felt better, but when digging deeper, it seemed like the code for the class Weak_String and the class Strong_String were nearly identical. Further, I could not decide which version of the Weak_String class I liked better, when operator== was true only when the case matched or when just the character order was sufficient. So, with all these similar types of strings that I might want to use in some future project, decided to remove the redundancy through making a templated class Multi_String. I also create an enum class to distinguish the different kinds of strings.

Multi_String.hpp

#ifndef MULTI_STRING
#define MULTI_STRING
#pragma once

#include <compare>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cassert>

enum class String_Type : uint8_t {
    regular,
    dictionary,
    case_equivalent,
    case_insensitve
};

template <String_Type ST>
class Multi_String;
template <String_Type ST>
std::ostream& operator<<(std::ostream& out, const Multi_String<ST>& str);

template <String_Type ST>
class Multi_String
{
    std::string s;
public:
    Multi_String(const char* cstr) : s{ cstr } {}
    Multi_String(const std::string& s_) : s{ s_ } {}
    Multi_String(std::string&& s_) noexcept : s{ std::move(s_) } {}
    auto operator<=>(const Multi_String<ST>& rhs) const;
    bool operator==(const Multi_String<ST>& rhs) noexcept;
    const char* c_str() const noexcept;

    friend std::ostream& operator<< (std::ostream& out, const Multi_String<ST>& s);
};


template<String_Type ST>
const char* Multi_String<ST>::c_str() const noexcept
{
    return s.c_str();
}

#pragma warning(push)
#pragma warning(disable:26486)
#pragma warning(disable:26489)
template<String_Type ST>
auto Multi_String<ST>::operator<=>(const Multi_String<ST>& rhs) const
{
    const auto cmp = [](const unsigned char c, const unsigned char d) {
        return std::tolower(c) == std::tolower(d);
    };
    if (s == rhs.s) return std::weak_ordering::equivalent;
    auto values = std::mismatch(s.cbegin(), s.cend(), rhs.s.cbegin(),
        rhs.s.cend(), cmp);
    if (values.first == s.cend()) {
        if (values.second == rhs.s.cend())  // no mismatches same length
            return std::weak_ordering::equivalent;
        return std::weak_ordering::less;    // s is shorter than rhs.s
    }
    if (values.second == rhs.s.cend()) return std::weak_ordering::greater;
    if (tolower(*(values.first)) < tolower(*(values.second))) {
        return std::weak_ordering::less;
    }
    return std::weak_ordering::greater;
}

template<>
auto Multi_String<String_Type::regular>::operator<=>
(const Multi_String<String_Type::regular>& rhs) const
{
    if (s == rhs.s) return std::strong_ordering::equal;
    if (s < rhs.s) return std::strong_ordering::less;
    return std::strong_ordering::greater;
}

template<>
auto Multi_String<String_Type::dictionary>::operator<=>
    (const Multi_String<String_Type::dictionary>& rhs) const
{
    const auto cmp = [](const unsigned char c, const unsigned char d) {
        return std::tolower(c) == std::tolower(d);
    };
    if (s == rhs.s) return std::strong_ordering::equal;
    auto values = std::mismatch(s.cbegin(), s.cend(),
        rhs.s.cbegin(), rhs.s.cend(), cmp);
    if (values.first == s.cend()) {
        if (values.second == rhs.s.cend()) {    // no mismatches same length
            const auto comp = strcmp(s.c_str(), rhs.s.c_str()); // uppercase first
            if (comp < 0) return std::strong_ordering::less;
            return std::strong_ordering::greater;
        }
        return std::strong_ordering::less;      // s is shorter than rhs.s
    }
    if (values.second == rhs.s.cend()) return std::strong_ordering::greater;
    if (tolower(*(values.first)) < tolower(*(values.second))) {
        return std::strong_ordering::less;
    }
    return std::strong_ordering::greater;
}
#pragma warning (pop)

template<String_Type ST>
bool Multi_String<ST>::operator==(const Multi_String<ST>& rhs) noexcept {
    return this->s == rhs.s;        // Distinguish between equal and equivalent
}

template<>
bool Multi_String<String_Type::case_insensitve>::operator==
(const Multi_String<String_Type::case_insensitve>& rhs) noexcept {
    return (*this <=> rhs) == 0;    // Case Insensitive
}

std::ostream& operator<<(std::ostream& out, 
    const Multi_String<String_Type::regular>& str) {
    out << str.s;
    return out;
}

std::ostream& operator<<(std::ostream& out, 
    const Multi_String<String_Type::dictionary>& str) {
    out << str.s;
    return out;
}

std::ostream& operator<<(std::ostream& out, 
    const Multi_String<String_Type::case_equivalent>& str) {
    out << str.s;
    return out;
}

std::ostream& operator<<(std::ostream& out, 
    const Multi_String<String_Type::case_insensitve>& str) {
    out << str.s;
    return out;
}

#endif // !MULTI_STRING

I tried, but I was unable to combine the two identical versions of cmp within the body of the class. I was also unable to get a cleaner version of operator<<, but since there are only four cases, this was OK.

Additionally, I wanted to compare my case insensitive string to that of Mr. Sutter's GOTW #29 which manipulates the class trait of the std::basic_string. Both functions perform identically in Mr. Sutter's tests. I show this code here:

// Herb Sutter solution to make case-insensitive strings
// Manipulate the "class traits" to lose case sensitivity
// for more see: http://gotw.ca/gotw/029.htm
// 
// CI_String.hpp
//
#ifndef CI_STRING
#define CI_STRING

#pragma once

#include <string>
#include <cassert>
using std::char_traits;
using std::basic_string;

struct ci_char_traits : public char_traits<char>
    // just inherit all the other functions
    //  that we don't need to override
{
    static bool eq(char c1, char c2);
    static bool ne(char c1, char c2);
    static bool lt(char c1, char c2);
    static int compare(const char* s1, const char* s2, size_t n);
    static const char* find(const char* s, int n, char a);
};

using ci_String = basic_string<char, ci_char_traits>;

#endif // !CI_STRING

//
// CI_String.cpp
//
#include "CI_String.hpp"
#pragma warning(push)
#pragma warning(disable:26440)
#pragma warning(disable:26489)
#pragma warning(disable:26481)

bool ci_char_traits::eq(char c1, char c2)
{
    return toupper(c1) == toupper(c2);
}

bool ci_char_traits::ne(char c1, char c2)
{
    return toupper(c1) != toupper(c2);
}

bool ci_char_traits::lt(char c1, char c2)
{
    return toupper(c1) < toupper(c2);
}

int ci_char_traits::compare(const char* s1, const char* s2, size_t n)
{
    assert(s1 != nullptr);
    assert(s2 != nullptr);

    return _memicmp(s1, s2, n);
}

const char* ci_char_traits::find(const char* s, int n, char a)
[[gsl::suppress(26487)]]
{
    assert(s != nullptr);
    while (n-- > 0 && toupper(*s) != toupper(a)) {
        ++s;
    }
    return s;
}

As well as the reworked main program:

#include "Multi_String.hpp"
#include "CI_String.hpp"    
#include <compare>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cassert>
#include <memory>
#include <random>

constexpr int sign(int i) noexcept
{
    return i < 0 ? -1 : i > 0 ? +1 : 0;
}

template<typename T>
int spaceship(T a, T b) noexcept
{
    return a < b ? -123 : a > b ? +123 : 0;
}

std::string op(int cmp)
{
    return cmp < 0 ? "<" : cmp > 0 ? ">" : "==";
}

template<typename T>
void test_spaceship(const std::vector<T>& elements)
{
    bool error = false;

    [[gsl::suppress(26446)]]
    for (std::size_t i = 0; i < elements.size(); ++i) {
        for (std::size_t j = 0; j < elements.size(); ++j) {
            const int expected = spaceship(i, j);
            const int actual = spaceship(elements[i], elements[j]);
            if (sign(expected) != sign(actual)) {
                std::cout << __func__ << ":\n";
                std::cout << "  expected " << elements[i] << " "
                    << op(expected) << " " << elements[j] << "\n";
                std::cout << "  but got  " << elements[i] << " "
                    << op(actual) << " " << elements[j] << "\n";
                error = true;
            }
        }
    }
    std::flush(std::cerr);
}

using EQ_String = Multi_String<String_Type::case_equivalent>;
using CI_String = Multi_String<String_Type::case_insensitve>;
using R_String = Multi_String<String_Type::regular>;
using D_String = Multi_String<String_Type::dictionary>;

#pragma warning(push)
#pragma warning(disable:26486)
int main()
{
    std::vector<D_String> dictionary_vector = { "cat","CAT","DOG","dog",
        "fish","Dog","" };

    std::cout << "Before sort          : ";
    for (const auto& s : dictionary_vector) std::cout << s << " ";
    std::cout << "\n";
    std::sort(dictionary_vector.begin(), dictionary_vector.end());

    std::cout << "After dictionary sort: ";
    std::vector<CI_String> ci_vector;
    std::vector<EQ_String> eq_vector;
    std::vector<R_String> regular_vector;
    std::vector<ci_String> sutter_vector;
    for (const auto& s : dictionary_vector) {
        std::cout << s << " ";
        ci_vector.emplace_back(s.c_str());
        eq_vector.emplace_back(s.c_str());
        regular_vector.emplace_back(s.c_str());
        sutter_vector.emplace_back(s.c_str());
    }
    std::cout << "\n\n";

    std::cout << "CI_String:\n";
    test_spaceship(ci_vector);
    std::cout << "\n";

    std::cout << "EQ_String:\n";
    test_spaceship(eq_vector);
    std::cout << "\n";

    std::cout << "R_String:\n";
    test_spaceship(regular_vector);
    std::cout << "\n";

    auto ci_last = std::unique(ci_vector.begin(), ci_vector.end());
    if (ci_last == ci_vector.end())
        std::cout << "CI_vector is UNIQUE!\n";
    else std::cout << "CI_vector is not UNIQUE!\n";
    ci_vector.erase(ci_last, ci_vector.end());
    for(const auto & s : ci_vector) std::cout << s << " ";
    std::cout << "\n\n";

    auto sutter_last = std::unique(sutter_vector.begin(), sutter_vector.end());
    if (sutter_last == sutter_vector.end())
        std::cout << "Sutter ci_vector is UNIQUE!\n";
    else std::cout << "Sutter ci_vector is not UNIQUE!\n";
    sutter_vector.erase(sutter_last, sutter_vector.end());
    for (const auto& s : sutter_vector) std::cout << s.c_str() << " ";
    std::cout << "\n\n";

    auto rd = std::random_device();
    auto e1 = std::default_random_engine(rd());
    std::shuffle(eq_vector.begin(), eq_vector.end(), e1);
    std::cout << "EQ_vector shuffled: ";
    for (const auto& s : eq_vector) std::cout << s << " ";
    std::cout << "\n";
    std::sort(eq_vector.begin(), eq_vector.end());
    std::cout << "EQ_vector sort    : ";
    for (const auto& s : eq_vector) std::cout << s << " ";
    std::cout << "\n";

    auto eq_last = std::unique(eq_vector.begin(), eq_vector.end());
    if (eq_last == eq_vector.end())
        std::cout << "EQ_vector is UNIQUE!\n";
    else std::cout << "EQ_vector is not UNIQUE!\n";
    eq_vector.erase(eq_last, eq_vector.end());
    for(const auto & s : eq_vector) std::cout << s << " ";
    std::cout << "\n\n";

    CI_String weak_test{ "AbCdE" };
    assert(weak_test == "abcde");
    assert(weak_test == "ABCDE");
    assert(std::strcmp(weak_test.c_str(), "AbCdE") == 0);
    assert(std::strcmp(weak_test.c_str(), "abcde") != 0);

    std::cout << "success for CI_String: " << weak_test.c_str() << "\n\n";

    ci_String sutter_test{ "AbCdE" };
    assert(sutter_test == "abcde");
    assert(sutter_test == "ABCDE");
    assert(std::strcmp(sutter_test.c_str(), "AbCdE") == 0);
    assert(std::strcmp(sutter_test.c_str(), "abcde") != 0);

    std::cout << "success for sutter ci_string: "
        << sutter_test.c_str() << "\n\n";
}
#pragma warning(pop)

And Output:

Before sort          : cat CAT DOG dog fish Dog
After dictionary sort:  CAT cat DOG Dog dog fish

CI_String:
test_spaceship:
  expected CAT < cat
  but got  CAT == cat
test_spaceship:
  expected cat > CAT
  but got  cat == CAT
test_spaceship:
  expected DOG < Dog
  but got  DOG == Dog
test_spaceship:
  expected DOG < dog
  but got  DOG == dog
test_spaceship:
  expected Dog > DOG
  but got  Dog == DOG
test_spaceship:
  expected Dog < dog
  but got  Dog == dog
test_spaceship:
  expected dog > DOG
  but got  dog == DOG
test_spaceship:
  expected dog > Dog
  but got  dog == Dog

EQ_String:
test_spaceship:
  expected CAT < cat
  but got  CAT == cat
test_spaceship:
  expected cat > CAT
  but got  cat == CAT
test_spaceship:
  expected DOG < Dog
  but got  DOG == Dog
test_spaceship:
  expected DOG < dog
  but got  DOG == dog
test_spaceship:
  expected Dog > DOG
  but got  Dog == DOG
test_spaceship:
  expected Dog < dog
  but got  Dog == dog
test_spaceship:
  expected dog > DOG
  but got  dog == DOG
test_spaceship:
  expected dog > Dog
  but got  dog == Dog

R_String:
test_spaceship:
  expected cat < DOG
  but got  cat > DOG
test_spaceship:
  expected cat < Dog
  but got  cat > Dog
test_spaceship:
  expected DOG > cat
  but got  DOG < cat
test_spaceship:
  expected Dog > cat
  but got  Dog < cat

CI_vector is not UNIQUE!
 CAT DOG fish

Sutter ci_vector is not UNIQUE!
 CAT DOG fish

EQ_vector shuffled: CAT Dog DOG dog  cat fish
EQ_vector sort    :  CAT cat Dog DOG dog fish
EQ_vector is UNIQUE!
 CAT cat Dog DOG dog fish

success for CI_String: AbCdE

success for sutter ci_string: AbCdE

Notice that the case_equivalent (EQ_String) and case_insensitive (CI_String) designations give the same equivalency answers when sorting, but find a very different set of unique members. Also notice that the order for the regular vs. dictionary sorts differ only in how the capital letters get sorted. I hope this helps

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.