Будет ли законно, чтобы std::set был специализирован для (u)int8 и символов с использованием набора битов и общего статического массива?

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

При этом: если какая-то реализация std::set была реализована с использованием битового набора для каждого экземпляра и статического массива из 256 значений, которые являются общими (это безопасно, поскольку ключи являются константами), было бы это законным в соответствии с (если издание имеет значение, тогда предположим С++ 20) стандарт?


person NoSenseEtAl    schedule 04.05.2019    source источник
comment
это безопасно, так как ключи являются константами C++17 как бы ломает это. insert и extract позволяют удалять, изменять и вставлять ключ обратно в ассоциативные контейнеры. Специализация должна была бы иметь дело с этой сложностью, гарантированной стандартом.   -  person NathanOliver    schedule 04.05.2019
comment
@NathanOliver Я думаю, что node_type может просто содержать непосредственно изменяемый член value_type. Ему никогда не нужно представлять значение, которое также находится в каком-то наборе, другом узле и т. д. insert(node) просто нужно будет сделать то же самое, что и insert(node.value()), которое уже равно O (1).   -  person aschepler    schedule 04.05.2019
comment
Обратите внимание, что вы можете сделать это только как реализацию, потому что в противном случае (теоретически) реализация может уже иметь специализацию.   -  person L. F.    schedule 04.05.2019
comment
@aschepler Можно получить ссылку на сохраненный объект, затем извлечь узел, изменить его, вставить обратно (в тот же или другой набор), и ссылка по-прежнему должна быть действительной. Так что нет, не получится.   -  person n. 1.8e9-where's-my-share m.    schedule 17.05.2019
comment
Почему это (если бы это было возможно) увеличило время компиляции? Эти специализации позволят избежать создания экземпляров шаблонов и могут даже избежать генерации кода (но, вероятно, не по причинам встраивания).   -  person Davis Herring    schedule 23.02.2021
comment
@DavisHerring Мое глупое предположение заключалось в том, что если у вас есть специализация шаблона, это увеличивает стоимость использования базового шаблона, поскольку компилятору нужно проверить, какой шаблон вы хотите ... но то, что вы говорите, также имеет смысл ...   -  person NoSenseEtAl    schedule 23.02.2021


Ответы (3)


Я не вижу ограничений, которые запрещали бы вам делать специализированную реализацию, если вы соблюдаете стандартные спецификации в разделе [set].

Для set<char> или set<uint8_t> вам потребуется 32 октета для хранения 256 битов, представляющих потенциальные элементы, с преимуществом очень быстрых операций над множествами. Для set<int> вы бы потребляли слишком много памяти, и это было бы оправдано IMHO только в том случае, если бы у вас были очень заполненные наборы.

При этом есть некоторые проблемы, которые необходимо преодолеть:

  • вам нужно будет организовать массив, который отображает значения в битовые позиции, чтобы он соответствовал предоставленному компаратору (стоимость при построении, если вы не можете поделиться им)
  • вам придется реализовать итератор (но это не проблема, так как растровое изображение и битовое смещение могут работать).
  • поскольку C++17 существует открытое предположение, что структура данных использует узлы поскольку существует элемент extract(), который должен возвращать значение (не указан) специализированный тип node_type. Не уверен, что подразумевает это требование, но я думаю, что его можно решить таким же образом, как и проблему с итератором выше.
  • Вам нужно будет соответствовать требованиям сложности (см. комментарий Натана Оливье к вашему вопросу). Трудность возникает из-за упорядочения общего массива. Однако, если бы вы использовали два общих массива (один для преобразования значения в битовое смещение и один для преобразования битового смещения в значение) или один массив пар, вы могли бы вставить что угодно в O (1).
person Christophe    schedule 04.05.2019
comment
Почему set<int8_t> работает иначе, чем set<char> (при условии, что CHAR_BIT==8)? - person aschepler; 04.05.2019
comment
Я думаю, что реализация требований к итератору swap также может быть сложной. - person JVApen; 04.05.2019
comment
@JVApen не уверен: set::swap меняет местами два набора с одинаковым базовым типом , так та же специализация. Просто поменяйте растровые изображения и указатель на общий массив. - person Christophe; 04.05.2019
comment
Ах, да, это возможно. Я думал, что это обычный член. Наверное плохо прочитал вопрос. - person JVApen; 04.05.2019
comment
Без проверки я думаю, что правила аннулирования итератора для set сделают это трудным, если не невозможным. - person Marshall Clow; 04.05.2019
comment
@MarshallClow с предложенной идеей во втором пункте итератор останется действительным после замены (поскольку битовое смещение не изменится). - person Christophe; 04.05.2019
comment
@ Кристоф Меня больше беспокоят вставка и удаление. - person Marshall Clow; 04.05.2019
comment
@MarshallClow Если бы вы всегда могли поддерживать итератор в силе, поскольку он относится к статическим данным, у вас все еще были бы проблемы с аннулированием? (На самом деле проверка, так как интератор может оставаться в силе после аннулирования) - person JVApen; 05.05.2019
comment
рассмотреть std::set<int> s = {0,2,4,6,8}; auto iter = s.find(4); s.insert(3); Что такое *iter сейчас? - person Marshall Clow; 06.05.2019
comment
@MarshallClow, согласно моему второму пункту, он все равно будет действителен: необработанные данные набора будут в двоичном формате 101010101, за которым следует 247, умноженное на 0, и указатель на общую таблицу, содержащую числа от 0 до 255, число i находится или нет в устанавливается в зависимости от бита i. Итератор будет представлен чем-то вроде указателя на необработанные данные набора и битового смещения. В случае find(4) это смещение будет равно 4. Вставка(3) установит бит (необработанные данные теперь 1011101010...0). Адрес необработанных данных не изменился, как и порядок битов: итератор остается действительным, как того требует С++ 17 :-) - person Christophe; 06.05.2019
comment
@Christophe Я думаю, что у меня есть пограничный случай, который может служить ограничением для создания специализированной реализации - person Leon; 01.03.2021

РЕДАКТИРОВАТЬ: Сделал ошибку. Прокси-итераторы не разрешены в C++17.

Я думаю, что это невозможно. Во-первых: эта реализация будет содержать все гарантии сложности и для большинства из них будет лучше, чем требования.

Во-вторых: вам не разрешено создавать прокси-итератор при использовании стандартного контейнера, поскольку в некоторых точках они должны были возвращать реальные ссылки. (Упомянутый @Christophe std::bitset не является контейнером и в его определении есть прокси-ссылка. std::vector ‹ bool > — известный пример нарушения гарантий). Таким образом, невозможно использовать эту реализацию.

Редактировать: спасибо @NicolBolas за указание на то, что итераторы прокси по-прежнему запрещены.

person user6556709    schedule 04.05.2019
comment
@NicolBolas Извините, моя вина. Это было предложено только для С++ 17 и отброшено. - person user6556709; 04.05.2019
comment
Поскольку возвращаемое значение std::set::iterator всегда const, я думаю, что если у вас есть постоянный вектор/массив со всеми возможными значениями и он возвращает значение с помощью const-ref, это не использует прокси-объекты - person JVApen; 05.05.2019
comment
Заботится ли стандарт о том, можете ли вы реализовать лучше, чем то, что указано как временная сложность? Вы не должны делать хуже. - person JVApen; 05.05.2019
comment
@JVApen Он хочет сохранить растровое изображение. Это означает 1 бит для каждого значения, а не массив/вектор со всеми значениями. Другое дело было плохо сформулировано. Это нормально быть лучше. Я должен был уточнить это после редактирования моего исходного ответа. - person user6556709; 05.05.2019
comment
@JVApen Теперь я понимаю, что вы хотите сделать. Я не уверен, что ваш оператор ++ итератора со сложностью O (n) является законным. - person user6556709; 05.05.2019
comment
begin() должно быть постоянным во времени, хотя это возможно, если вы сохраняете дополнительное состояние. (или если бы можно было в постоянное время найти первый с помощью некоторой инструкции ЦП) - person JVApen; 05.05.2019
comment
@JVApen Я не думаю, что есть процессор, который возвращает вам следующий бит в установленном байте. Но вы можете обмануть и просто всегда проверять все биты, которые будут иметь постоянное время выполнения. Другое дело, я думаю, вы можете утверждать, что даже линейная операция имеет постоянную сложность, потому что это означает только то, что вы можете найти абсолютную верхнюю границу для необходимых операций. Короче говоря: я думаю, что вы правы. - person user6556709; 05.05.2019

Ответ Кристофа содержит важный момент:

  • вам нужно будет организовать массив, который сопоставляет значения с битовыми позициями, так, чтобы он соответствовал предоставленному компаратору (стоимость при построении, если вы не можете поделиться им)

Это указывает на сложную проблему работы с компараторами с сохранением состояния (когда отношение порядка не является фиксированным, но может управляться извне и динамически изменяться во время выполнения). Хотя не одобряется общими людьми C++, я не мог найти ничего в стандарте, который запрещал бы компараторы с отслеживанием состояния для ассоциативных контейнеров (но я не искал слишком много). Если вы управляете состоянием компаратора и содержимым набора так, чтобы требование упорядочения никогда не нарушалось, то это работает на практике (по крайней мере, это сработало для меня).

Итак, мой вердикт таков: в самом общем случае произвольный компаратор std::set не может специализироваться для небольших целочисленных типов с использованием bitset и любых поддерживающих общих статических данных.

person Leon    schedule 01.03.2021