у меня регулярно возникает задача поиска в таких контейнерах, но к сожалению find жестко требует передать ему Account
приходиться создавать временный объект, что обременительно и весьма медленно
Account tmp;
tmp.id = 7;
auto it = idOrdered.find(tmp);
мне бы хотелось написать
auto it = idOrdered.find(7);
пару лет назад нам понадобился собственный контейнер, в котором мы расширили интерфейс добавив
у меня было подозрение, что поиск сделан нешаблонным не случайно и ожидал каких-то проблем с ним, но ровным счетом ничего не произошло.
Какими соображениями могли руководствоваться авторы стандарта, когда не сделали поиск шаблонным?
Если template <typename Type> iterator find_t(const Type& _Val) — это bad design, то почему?
Как насчет сделать собственный explicit конструктор из integer? Если это слишком опасно, можно сделать какой-нибудь SearchAccountById, для которого написать конструктор из integer, и сделать конструктор из SearchAccountById в Account.
V>Как насчет сделать собственный explicit конструктор из integer? Если это слишком опасно, можно сделать какой-нибудь SearchAccountById, для которого написать конструктор из integer, и сделать конструктор из SearchAccountById в Account.
Воркэраунды в лоб — неэффективны, если внутри создаваемого объекта есть такие полезняшки как строки и другие контейнеры, то его как ни крути, а конструктор будет дорог. Конструкторы реально получаются дороже чем поиск, а если это хэш-контейнеры — то бывает что и в разы дороже.
rm822:
R>у меня регулярно возникает задача поиска в таких контейнерах, но к сожалению find жестко требует передать ему Account R>приходиться создавать временный объект, что обременительно и весьма медленно
+1, я тоже обращал внимание на такую проблему. Это может не только снизить эффективность, но ещё и воспрепятствовать обеспечению гарантии бессбойности поиска (если конструирование временного объекта способно повлечь генерацию исключения).
R>Если template <typename Type> iterator find_t(const Type& _Val) — это bad design, то почему?
По-моему, это как раз был бы правильный дизайн. Попробую спросить у LWG что там думают по этому поводу.
R>у меня регулярно возникает задача поиска в таких контейнерах, но к сожалению find жестко требует передать ему Account R>приходиться создавать временный объект, что обременительно и весьма медленно R>
R>Account tmp;
R>tmp.id = 7;
R>auto it = idOrdered.find(tmp);
R>
R>мне бы хотелось написать R>
R>auto it = idOrdered.find(7);
R>
R>пару лет назад нам понадобился собственный контейнер, в котором мы расширили интерфейс добавив R>
R>у меня было подозрение, что поиск сделан нешаблонным не случайно и ожидал каких-то проблем с ним, но ровным счетом ничего не произошло. R>Какими соображениями могли руководствоваться авторы стандарта, когда не сделали поиск шаблонным? R>Если template <typename Type> iterator find_t(const Type& _Val) — это bad design, то почему?
Почему бы вам не воспользоваться контейнером std::map, упростив ключ до значения типа long?
Здравствуйте, rm822, Вы писали:
R>у меня регулярно возникает задача поиска в таких контейнерах, но к сожалению find жестко требует передать ему Account R>приходиться создавать временный объект, что обременительно и весьма медленно
Можно посмотреть в сторону boost::multi_index, он умеет сортировать (и, соответственно, искать) в том числе по ключу-члену элемента.
(В отличие от std::map, где ключ вынесен вовне, и std::set, где ключ является элементом).
R>у меня было подозрение, что поиск сделан нешаблонным не случайно R>Какими соображениями могли руководствоваться авторы стандарта, когда не сделали поиск шаблонным?
Прежде всего, идеологическая чистота. Как ты уже заметил, для работы с элементами и ключами нужно ввести полиморфную функцию — определённую на сочетаниях типов элемента и ключа.
Если (что вполне естественно) такой функцией является оператор <, то встаёт вопрос о согласованности его для всех типов, между которыми он определён.
Для строк (std::string + const char*) уже возникают тонкие места — char*<char* упорядочен иначе, чем остальные сочетания. (С другой стороны, char*<char* — хорошо известная мина, на которой можно легко подорваться — set<char*> — и которую так же легко можно обойти).
В остальных случаях — пользователь сам себе злой или добрый буратино, если уже делает элементы и ключи, пусть позаботится о корректной аксиоматике.
Ясно же, что для мономорфной функции корректность обеспечить легче, чем для полиморфной, особенно если это не шаблон и не перегрузка в рамках одного класса, а перегрузка глобальной функции (разбросанной по компилятору и библиотекам, как в случае оператора < ).
Второй момент. Целостность STL.
std::set параметризован классом компаратора, который опосредует эту самую функцию. По умолчанию это std::less<key_type>.
Можно было бы сделать жизнь беззаботной, введя универсальные классы — посредники операторов.
Т.е. вместо std::less<T> — был бы std::less_all и соответственно, std::set<Key, less_all<Key> >
И тут мы сталкиваемся с тем, что такой less_all перестаёт быть моделью std::binary_function.
Во-первых, произвольные типы аргументов. Ну, для С++ это никогда не было проблемой, хотя ещё встаёт вопрос — передавать их по значению или константной ссылке, — а вдруг исходный оператор <(L&,R&) принимает неконстантные ссылки?
Во-вторых, прибитый гвоздями тип результата — а вдруг исходный оператор возвращает что-то совсем другое? (Делаем поправку на ССЗБ, если мы вдруг решили сделать std::set над каким-то expression template).
В эпоху древних компиляторов на заре STL — <functional> с его std::bind1st и т.п. был лучшим из худшего. А для этого и пришлось наделать классы функций (std::less, std::plus и т.п.) и метафункций (std::not1 и т.п.) в единой модели — std::unary_function, std::binary_function.
Вот поэтому less_all, как минимум, выпадал из концепции. А как максимум — не факт, что не довёл бы компилятора до internal error.
В настоящий момент мы имеем тяжёлое наследие. Зато концептуально чистое и отлаженное.
R>Если template <typename Type> iterator find_t(const Type& _Val) — это bad design, то почему?
bad здесь лишь то, что можно в качестве ключа подсунуть любой тип. И попасть на случайную ветку нашего полиморфного компаратора, которая будет вести себя неожиданным образом.
Например, вместо сигнатуры (элемент,ключ) компилятор предпочтёт сконструировать временный элемент и взять сигнатуру (элемент,элемент).
Конечно, для этого надо подобрать условия — чтобы Type приводился к ключу менее предпочтительным способом, чем к элементу. Но вполне реально.
Или, если Type в равной степени приводится к ключу и элементу — получим ошибку неоднозначности. Вот это гораздо проще.
Или, не дай бог, наилучшая сигнатура лежит за пределами тех, которые мы специально написали (глобальный оператор <) и ломается логика.
А если до этих граблей в конкретном случае удалось не довести — то вполне себе good.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, rm822, Вы писали:
R>>у меня регулярно возникает задача поиска в таких контейнерах, но к сожалению find жестко требует передать ему Account R>>приходиться создавать временный объект, что обременительно и весьма медленно
К>Можно посмотреть в сторону boost::multi_index, он умеет сортировать (и, соответственно, искать) в том числе по ключу-члену элемента. К>(В отличие от std::map, где ключ вынесен вовне, и std::set, где ключ является элементом).
Самое главное в boost::multi_index то, что функция поиска там с шаблонным ключом. То, что требуется топикстартеру. Т.е. можно писать так:
R>у меня регулярно возникает задача поиска в таких контейнерах, но к сожалению find жестко требует передать ему Account R>приходиться создавать временный объект, что обременительно и весьма медленно R>
R>Account tmp;
R>tmp.id = 7;
R>auto it = idOrdered.find(tmp);
R>
если обременительно писать код, пишите эксплисит конструкторы
если медленно, пишите конструкторы простые, чтоб объект был достаточен для сравнения, и компилятор за вас все оптимизирует
R>мне бы хотелось написать R>
Здравствуйте, rm822, Вы писали:
R>у меня было подозрение, что поиск сделан нешаблонным не случайно и ожидал каких-то проблем с ним, но ровным счетом ничего не произошло. R>Какими соображениями могли руководствоваться авторы стандарта, когда не сделали поиск шаблонным? R>Если template <typename Type> iterator find_t(const Type& _Val) — это bad design, то почему?
Я бы сделал такой дизайн
template<Value, Key = Value, Less = std::set_less<Value, Key>>
class set{};
Кодт:
R>>у меня было подозрение, что поиск сделан нешаблонным не случайно R>>Какими соображениями могли руководствоваться авторы стандарта, когда не сделали поиск шаблонным?
К>Прежде всего, идеологическая чистота. Как ты уже заметил, для работы с элементами и ключами нужно ввести полиморфную функцию — определённую на сочетаниях типов элемента и ключа. К>Если (что вполне естественно) такой функцией является оператор <, то встаёт вопрос о согласованности его для всех типов, между которыми он определён. К>Для строк (std::string + const char*) уже возникают тонкие места
Пример с std::string + const char* — это, наверное, как раз самый распространённый случай вынужденного overhead-а. std::string прекрасно сравнивается с char const* без преобразований, но задействовать такое эффективное и бессбойное сравнение при поиске ключа в ассоциативном контейнере мы не можем.
К>char*<char* упорядочен иначе, чем остальные сочетания. (С другой стороны, char*<char* — хорошо известная мина, на которой можно легко подорваться — set<char*> — и которую так же легко можно обойти).
Это явно не проблемы гетерогенного сравнения.
К>Ясно же, что для мономорфной функции корректность обеспечить легче, чем для полиморфной, особенно если это не шаблон и не перегрузка в рамках одного класса, а перегрузка глобальной функции (разбросанной по компилятору и библиотекам, как в случае оператора < ).
Убирать нешаблонную функцию find не надо (это может навредить эффективности в некоторых случаях), шаблонный вариант find (с другим именем) следует добавить к тому, что уже имеется.
К>Второй момент. Целостность STL. К>std::set параметризован классом компаратора, который опосредует эту самую функцию. По умолчанию это std::less<key_type>. К>Можно было бы сделать жизнь беззаботной, введя универсальные классы — посредники операторов. К>Т.е. вместо std::less<T> — был бы std::less_all и соответственно, std::set<Key, less_all<Key> > К>
К>И тут мы сталкиваемся с тем, что такой less_all перестаёт быть моделью std::binary_function.
std::binary_function — это костыль, который даже в рамках C++03 для реализации map-а не нужен абсолютно. С появлением decltype и "perfect forwarding" надобность в нём отпала глобально, и теперь он является deprecated:
C++11 — D.8.1:
The class templates unary_function and binary_function are deprecated. A program shall not declare specializations of these templates.
К>Во-первых, произвольные типы аргументов. Ну, для С++ это никогда не было проблемой, хотя ещё встаёт вопрос — передавать их по значению или константной ссылке, — а вдруг исходный оператор <(L&,R&) принимает неконстантные ссылки?
Это странное сравнение, такое вполне можно запретить. В C++11 хохмы ради можно было бы использовать универсальные L&& и R&&, хотя я не вижу в этом смысла.
К>Во-вторых, прибитый гвоздями тип результата — а вдруг исходный оператор возвращает что-то совсем другое?
В случае less он по-любому прибит гвоздями. Операция сравнения со стандартной семантикой должна возвращать булево значение (или хотя бы то, что к нему приводится). Подсовывать что-то другое ассоциативному контейнеру бессмысленно — ему это явно не понравится.
J>Самое главное в boost::multi_index то, что функция поиска там с шаблонным ключом. То, что требуется топикстартеру.
вообще-то разговор идет про правильный дизайн, а не про то где взять готовую реализацию.
Просто для ясности — мультииндекс мне не нужен совершенно
Здравствуйте, rm822, Вы писали:
J>>Самое главное в boost::multi_index то, что функция поиска там с шаблонным ключом. То, что требуется топикстартеру. R>вообще-то разговор идет про правильный дизайн, а не про то где взять готовую реализацию. R>Просто для ясности — мультииндекс мне не нужен совершенно
multi_index и есть правильный дизайн а set/map — его жалкие подобия
я думаю, что если бы скорость поиска была критична, то вопрос автора темы был бы другим.
тогда бы ему и были предложены различные варианты контейнеров.
я предлагал наиболее простой на мой взгляд вариант решения имеющейся проблемы без изменения того, что уже написано.