find в stl контейнерах
От: rm822 Россия  
Дата: 12.01.12 22:29
Оценка: 3 (1) +1
рассмотрим такой контейнер
std::set<Account, Account::LessById> idOrdered;


у меня регулярно возникает задача поиска в таких контейнерах, но к сожалению 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);


при этом, конечно, пришлось расширить и сигнатуры less
struct LessById
{
  bool operator()(const Account& l, const Account& r) const;
  bool operator()(long l, const Account& r) const;
  bool operator()(const Account& l, long r) const;
}


у меня было подозрение, что поиск сделан нешаблонным не случайно и ожидал каких-то проблем с ним, но ровным счетом ничего не произошло.
Какими соображениями могли руководствоваться авторы стандарта, когда не сделали поиск шаблонным?
Если template <typename Type> iterator find_t(const Type& _Val) — это bad design, то почему?
вдогонку
От: rm822 Россия  
Дата: 12.01.12 22:32
Оценка:
если find_t — это bad design, то какие у него есть альтернативы с хорошим дизайном?
Re: find в stl контейнерах
От: Vamp Россия  
Дата: 12.01.12 22:36
Оценка:
мне бы хотелось написать
auto it = idOrdered.find(7);


Как насчет сделать собственный explicit конструктор из integer? Если это слишком опасно, можно сделать какой-нибудь SearchAccountById, для которого написать конструктор из integer, и сделать конструктор из SearchAccountById в Account.
Да здравствует мыло душистое и веревка пушистая.
Re[2]: find в stl контейнерах
От: rm822 Россия  
Дата: 12.01.12 22:47
Оценка:
V>Как насчет сделать собственный explicit конструктор из integer? Если это слишком опасно, можно сделать какой-нибудь SearchAccountById, для которого написать конструктор из integer, и сделать конструктор из SearchAccountById в Account.
Воркэраунды в лоб — неэффективны, если внутри создаваемого объекта есть такие полезняшки как строки и другие контейнеры, то его как ни крути, а конструктор будет дорог. Конструкторы реально получаются дороже чем поиск, а если это хэш-контейнеры — то бывает что и в разы дороже.
Re: find в stl контейнерах
От: Masterkent  
Дата: 12.01.12 22:56
Оценка: 10 (1)
rm822:

R>у меня регулярно возникает задача поиска в таких контейнерах, но к сожалению find жестко требует передать ему Account

R>приходиться создавать временный объект, что обременительно и весьма медленно

+1, я тоже обращал внимание на такую проблему. Это может не только снизить эффективность, но ещё и воспрепятствовать обеспечению гарантии бессбойности поиска (если конструирование временного объекта способно повлечь генерацию исключения).

R>Если template <typename Type> iterator find_t(const Type& _Val) — это bad design, то почему?


По-моему, это как раз был бы правильный дизайн. Попробую спросить у LWG что там думают по этому поводу.
Re: find в stl контейнерах
От: Сыроежка  
Дата: 12.01.12 23:36
Оценка: :)
Здравствуйте, rm822, Вы писали:

R>рассмотрим такой контейнер

R>
R>std::set<Account, Account::LessById> idOrdered;
R>


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>    template <typename Type>
R>    iterator find_t(const Type& _Val);
R>


R>при этом, конечно, пришлось расширить и сигнатуры less

R>
R>struct LessById
R>{
R>  bool operator()(const Account& l, const Account& r) const;
R>  bool operator()(long l, const Account& r) const;
R>  bool operator()(const Account& l, long r) const;
R>}
R>


R>у меня было подозрение, что поиск сделан нешаблонным не случайно и ожидал каких-то проблем с ним, но ровным счетом ничего не произошло.

R>Какими соображениями могли руководствоваться авторы стандарта, когда не сделали поиск шаблонным?
R>Если template <typename Type> iterator find_t(const Type& _Val) — это bad design, то почему?

Почему бы вам не воспользоваться контейнером std::map, упростив ключ до значения типа long?
Меня можно встретить на www.cpp.forum24.ru
Re[2]: find в stl контейнерах
От: nen777w  
Дата: 13.01.12 00:20
Оценка: 15 (1) :))) :)))
Здравствуйте, Сыроежка, Вы писали:
С>Почему бы вам не воспользоваться контейнером std::map, упростив ключ до значения типа long?

Сыроежка чем ты питаешься?
Re: find в stl контейнерах
От: Кодт Россия  
Дата: 13.01.12 04:20
Оценка: 6 (2)
Здравствуйте, 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> >
struct less_all
{
  typedef bool result_type;
  template<class L, class R> bool operator () (const L& l, const R& r) const { return l < r; }
};

И тут мы сталкиваемся с тем, что такой 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.
Перекуём баги на фичи!
Re: find в stl контейнерах
От: Myrgy Беларусь  
Дата: 13.01.12 06:59
Оценка: 3 (1) -1 :)
а вариант использования find_if не подходит?
не модифицируя класс можно написать несколько требуемых предикатов и использовать их.
Re[2]: find в stl контейнерах
От: johny5 Новая Зеландия
Дата: 13.01.12 07:36
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, rm822, Вы писали:


R>>у меня регулярно возникает задача поиска в таких контейнерах, но к сожалению find жестко требует передать ему Account

R>>приходиться создавать временный объект, что обременительно и весьма медленно

К>Можно посмотреть в сторону boost::multi_index, он умеет сортировать (и, соответственно, искать) в том числе по ключу-члену элемента.

К>(В отличие от std::map, где ключ вынесен вовне, и std::set, где ключ является элементом).

Самое главное в boost::multi_index то, что функция поиска там с шаблонным ключом. То, что требуется топикстартеру. Т.е. можно писать так:

struct AccountLess
{
  bool operator()(const Account& l, const Account& r) const;
  bool operator()(long l, const Account& r) const;
  bool operator()(const Account& l, long r) const;
};

// псевдокод..
boost::multi_index<Account, AccountLess>  cont;

auto it1 = cont.find( 5l );
auto it2 = cont.find( Account() );


И не надо при этом городить дополнительные индексы.
Re: find в stl контейнерах
От: ilnar Россия  
Дата: 13.01.12 09:10
Оценка:
Здравствуйте, rm822, Вы писали:

R>рассмотрим такой контейнер

R>
R>std::set<Account, Account::LessById> idOrdered;
R>


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>


имплисит конструкторы в помощь
Re: find в stl контейнерах
От: IROV..  
Дата: 13.01.12 12:34
Оценка:
Здравствуйте, 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{};
я не волшебник, я только учусь!
Re[2]: find в stl контейнерах
От: Masterkent  
Дата: 13.01.12 22:19
Оценка:
Myrgy:

M>а вариант использования find_if не подходит?


Меняем логарифмический перебор на линейный?
Re[2]: find в stl контейнерах
От: Masterkent  
Дата: 13.01.12 22:26
Оценка: 7 (2)
Кодт:

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> >
К>
К>struct less_all
К>{
К>  typedef bool result_type;
К>  template<class L, class R> bool operator () (const L& l, const R& r) const { return l < r; }
К>};
К>

К>И тут мы сталкиваемся с тем, что такой 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 он по-любому прибит гвоздями. Операция сравнения со стандартной семантикой должна возвращать булево значение (или хотя бы то, что к нему приводится). Подсовывать что-то другое ассоциативному контейнеру бессмысленно — ему это явно не понравится.
Re[3]: find в stl контейнерах
От: rm822 Россия  
Дата: 14.01.12 00:01
Оценка:
J>Самое главное в boost::multi_index то, что функция поиска там с шаблонным ключом. То, что требуется топикстартеру.
вообще-то разговор идет про правильный дизайн, а не про то где взять готовую реализацию.
Просто для ясности — мультииндекс мне не нужен совершенно
Re[4]: find в stl контейнерах
От: Кодт Россия  
Дата: 14.01.12 11:25
Оценка: 1 (1) +1 :)
Здравствуйте, rm822, Вы писали:

J>>Самое главное в boost::multi_index то, что функция поиска там с шаблонным ключом. То, что требуется топикстартеру.

R>вообще-то разговор идет про правильный дизайн, а не про то где взять готовую реализацию.
R>Просто для ясности — мультииндекс мне не нужен совершенно

multi_index и есть правильный дизайн а set/map — его жалкие подобия
Перекуём баги на фичи!
Re[5]: find в stl контейнерах
От: johny5 Новая Зеландия
Дата: 14.01.12 13:50
Оценка:
Здравствуйте, Кодт, Вы писали:

К>multi_index и есть правильный дизайн а set/map — его жалкие подобия


+1
И вообще, бессмысленно пинать за дизайн библиотеку, которая была разработана (и осталась неизменной) почти уже 20 лет тому назад
Re[6]: find в stl контейнерах
От: Masterkent  
Дата: 14.01.12 14:11
Оценка:
johny5:

J>И вообще, бессмысленно пинать за дизайн библиотеку, которая была разработана (и осталась неизменной) почти уже 20 лет тому назад


Во-первых, она не осталась неизменной. Во-вторых, пинать комитетчиков смысл есть, т.к. есть реальные шансы добиться улучшений (пусть и не сразу).
Re[3]: find в stl контейнерах
От: Myrgy Беларусь  
Дата: 15.01.12 07:38
Оценка: -2
я думаю, что если бы скорость поиска была критична, то вопрос автора темы был бы другим.
тогда бы ему и были предложены различные варианты контейнеров.
я предлагал наиболее простой на мой взгляд вариант решения имеющейся проблемы без изменения того, что уже написано.
Re: find в stl контейнерах
От: cruelbob  
Дата: 24.01.12 21:35
Оценка:
Почему бы просто не выделить память под объект Account и преобразовать указатель, тоесть:

Account *acc = operator new(sizeof(Account));
acc->id = 7;
auto it = idOrdered.find(acc);


таким образом создание объекта почти не займет времени!
Twitter — http://twitter.com/Cruelbob
Блог — http://cruelbob.blogspot.ru/
Мыло — vlad.kolotvin@gmail.com
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.