Сравнение строк STL, использующих разные распределители

Я хотел бы сравнить строки STL, которые выделяются разными распределителями, например. обычный std::string со строкой с использованием специализированного распределителя STL. К сожалению, кажется, что обычный operator==() не работает в этом случае:

// Custom STL allocator to allocate char for string class
typedef MyAllocator<char> MyCharAllocator;

// Define an instance of this allocator
MyCharAllocator myAlloc;

// An STL string with custom allocator
typedef std::basic_string
<
    char, 
    std::char_traits<char>, 
    MyCharAllocator
> 
CustomAllocString;

std::string s1("Hello");
CustomAllocString s2("Hello", myAlloc);

if (s1 == s2)  // <--- ERROR: doesn't compile
   ...

В частности, MSVC10 (VS2010 SP1) выдает следующее сообщение об ошибке:

ошибка C2678: двоичный '==': оператор не найден, который принимает левый операнд типа 'std::string' (или нет приемлемого преобразования)

Итак, более низкий (менее читаемый) код выглядит следующим образом:

if (strcmp(s1.c_str(), s2.c_str()) == 0)
   ...

.

(Это также особенно раздражает в случаях, когда, например, есть std::vector строк с разным распределением, где обычный простой синтаксис v[i] == w[j] не может быть использован.)

Это не очень хорошо для меня, поскольку пользовательский распределитель изменяет способ запроса строки памяти, но интерфейс строкового класса (включая сравнение с operator==()) не зависит от того, каким образом строка выделяет его память.

Что-то мне здесь не хватает? Возможно ли в этом случае поддерживать высокоуровневый интерфейс С++ и перегрузку оператора?

Ответ 1

Используйте std::lexicographical_compare для сравнения:

bool const lt = std::lexicographical_compare(s1.begin(), s1.end(),
                                             s2.begin(), s2.end());

Для сравнения равенства вы можете использовать std::equal:

bool const e = s1.length() == s2.length() &&
               std::equal(s1.begin(), s1.end(), s2.begin());

В качестве альтернативы вы можете просто вернуться на strcmp (или на самом деле memcmp, так как это имеет правильный seman & shy; tics; помните, что строка С++ является более общей, чем строка C), как вы предположили, что может poten & shy; используют некоторую магию более низкого уровня, например, сравнивая все машинное слово за раз (хотя вышеупомянутый алгоритм также может быть специализирован таким образом). Я бы сказал, измерить и сравнить. Для коротких строк стандартные библиотечные алгоритмы, по крайней мере, хорошо самоописательны.


Основываясь на идее @Dietmar ниже, вы можете перенести эти функции в шаблонные перегрузки:

#include <string>
#include <algorithm>

template <typename TChar,
          typename TTraits1, typename TAlloc1,
          typename TTraits2, typename TAlloc2>
bool operator==(std::basic_string<TChar, TTraits1, TAlloc1> const & s1,
                std::basic_string<TChar, TTraits2, TAlloc2> const & s2)
{
    return s1.length() == s2.length() &&
           std::equal(s1.begin(), s1.end(), s2.begin());
}

Пример использования:

#include <ext/malloc_allocator.h>
int main()
{
    std::string a("hello");
    std::basic_string<char, std::char_traits<char>, __gnu_cxx::malloc_allocator<char>> b("hello");
    return a == b;
}

Фактически вы можете определить такую ​​перегрузку для большинства стандартных контейнеров. Вы даже можете создать шаблон на шаблоне, но это было бы экстремально.

Ответ 2

Стандарт определяет только операторы, использующие гомогенные типы строк, т.е. все аргументы шаблона должны совпадать. Тем не менее, вы можете определить подходящий оператор равенства в пространстве имен, где задан распределитель: поиск по зависимостям зависит от него. Если вы решите реализовать свой собственный оператор присваивания, он будет выглядеть примерно так:

bool operator== (std::string const& s0,
                 std::basic_string<char, std::char_traits<char>, MyCharAllocator> const& s1) {
    return s0.size() == s1.size() && std::equal(s0.begin(), s0.end(), s1.begin()).first;
}

(плюс несколько других перегрузок). Принимая это на следующий уровень, может быть даже разумным определять версии различных операторов отношений в терминах требований к контейнерам и не ограничивать аргументы шаблона:

namespace my_alloc {
    template <typename T> class allocator { ... };
    template <typename T0, typename T1>
    bool operator== (T0 const& c0, T1 const& c1) {
        return c0.size() == c1.size() && std::equal(c0.begin(), c0.end(), c1.end);
    }
    ...
}

Очевидно, что операторы могут быть ограничены конкретными типами контейнеров, отличающимися только параметрами шаблона распределителя.

В отношении того, почему стандарт не определяет смешанные сравнения типов, основной причиной отсутствия поддержки сравнения смешанного типа, вероятно, является то, что на самом деле вы вообще не хотите смешивать распределители в своей программе! То есть, если вам нужно использовать распределитель, вы должны использовать тип распределителя, который инкапсулирует динамически полиморфную политику распределения и всегда использует результирующий тип распределителя. Причиной этого было бы то, что в противном случае вы получите либо несовместимый интерфейс, либо вам нужно будет сделать все шаблоном, т.е. Вы хотите сохранить некоторый уровень используемых типов лексики. Конечно, используя даже один дополнительный тип распределителя, у вас должно быть два типа строки словаря: экземпляр по умолчанию и экземпляр для вашего специального выделения.

Тем не менее, есть еще одна потенциальная причина не поддерживать смешанное сравнение типов: если operator==() действительно становится сопоставлением между двумя значениями, как это имеет место, если распределители отличаются, это может привести к значительно более широкому определению значения равенство: следует ли std::vector<T>() == std::deque<T> поддерживать? Если нет, то зачем сравнивать строки с разными распределителями? Конечно, распределитель является несущественным атрибутом std::basic_string<C, T, A>, который может быть хорошей причиной для его игнорирования. Я не уверен, что нужно поддерживать смешанное сравнение типов. Может быть разумным поддерживать операторов (это, вероятно, распространяется на другие операторы, чем operator==()) для типов контейнеров, отличающихся только их типами распределителей.