Що таке прозорі компаратори?


106

У C ++ 14 асоціативні контейнери, схоже, змінилися від C ++ 11 - [Associative.reqmts] / 13 говорить:

Шаблони функцій - членів find, count, lower_bound, upper_bound, і equal_rangeне повинен брати участь у вирішенні перевантаження , якщо тип Compare::is_transparentне існує.

Яка мета зробити компаратор «прозорим»?

C ++ 14 також пропонує такі шаблони бібліотеки:

template <class T = void> struct less {
    constexpr bool operator()(const T& x, const T& y) const;
    typedef T first_argument_type;
    typedef T second_argument_type;
    typedef bool result_type;
};

template <> struct less<void> {
    template <class T, class U> auto operator()(T&& t, U&& u) const
    -> decltype(std::forward<T>(t) < std::forward<U>(u));
    typedef *unspecified* is_transparent;
};

Так, наприклад, std::set<T, std::less<T>>було б НЕ мати прозорий компаратор, але std::set<T, std::less<>> буде мати один.

Яку проблему це вирішує і чи змінюється це функціонування стандартних контейнерів? Наприклад, параметри шаблону std::setпо - , як і раніше Key, Compare = std::less<Key>, ..., так само набір за замовчуванням втратить find, countі т.д. член?


Наприклад, дивіться цей опис cppreference . Зараз я почуваюся дурним, тому що я відзначаю слово " шаблон функції члена " ...
Керрек СБ

5
Можливо , пов'язані з : stackoverflow.com/questions/18939882 / ...

cppreference також має розмиття
Cubbi

Відповіді:


60

Яку проблему це вирішує,

Див відповідь Дітмар в і відповідь remyabel в .

і чи це змінює роботу стандартних контейнерів?

Ні, не за замовчуванням.

Нові шаблони функцій члена перевантаження findтощо дозволяють використовувати тип, який можна порівняти з ключем контейнера, замість того, щоб використовувати сам тип ключа. Дивіться N3465 від Joaquín Mª López Muñoz щодо обґрунтування та детальної, ретельно написаної пропозиції щодо доповнення цієї функції.

На зустрічі в Брістолі LWG погодилася, що функція гетерогенного пошуку корисна і бажана, але ми не могли бути впевнені, що пропозиція Хоакіна буде безпечною у всіх випадках. Пропозиція N3465 спричинила б серйозні проблеми для деяких програм (див. Розділ « Вплив на існуючий код» ). Joaquín підготував оновлений проект пропозиції з деякими альтернативними варіантами реалізації з різними вигодами, що було дуже корисно, щоб допомогти LWG зрозуміти плюси і мінуси, але всі вони ризикували певним чином порушити деякі програми, тому не було єдиної думки додати цю функцію. Ми вирішили, що хоч би не було безпечно додавати цю функцію беззастережно, але було б безпечно, якщо вона була відключена за замовчуванням та лише "включення".

Ключова відмінність пропозиції N3657 (яка була останньою доопрацюванням власноруч і STL на основі N3465 та пізніше не опублікованого проекту Жоакіна ) полягала в тому, щоб додати is_transparentтип як протокол, який можна використовувати для включення до нового функціоналу.

Якщо ви не використовуєте "прозорий функтор" (тобто той, який визначає is_transparentтип), то контейнери поводяться так само, як це було завжди, і це все ще за замовчуванням.

Якщо ви вирішите використовувати std::less<>(що є новим для C ++ 14) або іншим типом "прозорого функтора", тоді ви отримаєте нову функціональність.

Використання std::less<>легко з шаблонами псевдонімів:

template<typename T, typename Cmp = std::less<>, typename Alloc = std::allocator<T>>
  using set = std::set<T, Cmp, Alloc>;

Назва is_transparentпоходить від N3421 STL, який додав "операторів з діамантами" до C ++ 14. "Прозорий функтор" - це той, який приймає будь-які типи аргументів (які не повинні бути однаковими) і просто пересилає ці аргументи іншому оператору. Такий функтор має бути саме тим, що ви хочете для неоднорідного пошуку в асоціативних контейнерах, тому тип is_transparentдодавали до всіх операторів алмазів і використовували як тип тегу для вказівки нової функціональності, слід активувати в асоціативних контейнерах. Технічно контейнерам не потрібен "прозорий функтор", а лише той, який підтримує виклик його з неоднорідними типами (наприклад, pointer_compтип https://stackoverflow.com/a/18940595/981959 не є прозорим відповідно до визначення STL,pointer_comp::is_transparentдозволяє використовувати його для вирішення проблеми). Якщо ви коли-небудь шукаєте у своїх std::set<T, C>ключах типу Tабо intтоді його Cпотрібно називати лише аргументами типу Tта int(в будь-якому порядку), це не потрібно бути справді прозорим. Ми використовували це ім'я частково, тому що не змогли придумати кращого імені (я б вважав is_polymorphicза краще, тому що такі функціонери використовують статичний поліморфізм, але вже є std::is_polymorphicтипове ознака, яке відноситься до динамічного поліморфізму).


3
Гей, ви були тим, кому STL сказав: "Звичайно, ви можете зробити виведення аргументів шаблону в голові" в розмові, пов’язаній з ватаром?
Керрек СБ

10
Ні, я там не був, але є люди, у яких набагато більше відповідних укладачів, ніж у мене :)
Jonathan Wakely

Я думаю, що "оператор алмазів" посилається на <>пов'язану пропозицію, але ця пропозиція не була внесена <>- це існуючий синтаксис для порожнього списку параметрів шаблону. "Діамантові оператори" були б дещо менш заплутаними.
Кверті

33

У C ++ 11 тобто не шаблони членів find(), lower_bound()і т.д. Тобто, нічого не втрачається цією зміною. Шаблони членів були введені з n3657, щоб дозволити використовувати неоднорідні ключі з асоціативними контейнерами. Я не бачу конкретного прикладу, де це корисно, крім прикладу, який є добрим і поганим!

is_transparentВикористання призначене , щоб уникнути небажаних переходів. Якби шаблони учасників не обмежувались, існуючий код може проходити безпосередньо через об'єкти, які були б перетворені без шаблонів учасників. Приклад використання-випадку від n3657 - це розташування об'єкта у std::set<std::string>використанні рядкового літералу: з визначенням C ++ 11 std::stringоб'єкт будується при передачі рядкових літералів до відповідної функції-члена. Зі зміною можна використовувати прямий буквал безпосередньо. Якщо базовий об’єкт функції порівняння реалізується виключно з точки зору std::string, це погано, тому що зараз std::stringбуде створено a для кожного порівняння. З іншого боку, якщо базовий об'єкт функції порівняння може приймати astd::string і рядковий літерал, який може уникнути побудови тимчасового об'єкта.

Вкладений is_transparentтип в об'єкті функції порівняння забезпечує спосіб визначити, чи слід використовувати функцію шаблонного члена: якщо об’єкт функції порівняння може мати справу з неоднорідними аргументами, він визначає цей тип, щоб вказати, що він може ефективно працювати з різними аргументами. Наприклад, об'єкти нових функцій оператора просто делегуються operator<()та вимагають прозорості. Принаймні, це працює, для std::stringчого перевантажено менше, ніж оператори, які беруть char const*аргументи. Оскільки ці об’єкти функцій також є новими, навіть якщо вони роблять неправильно (тобто вимагають перетворення для якогось типу), це, принаймні, не буде тихою зміною, що призводить до погіршення продуктивності.


Спасибі - дивіться мого коментаря до іншого питання: Чи отримуєте ви прозору поведінку за замовчуванням?
Керрек СБ

8
@ KerkerSB: прозора поведінка вмикається, коли is_transparentвизначено об'єктом функції порівняння відповідно до пункту 13. [асоціативний.рекмтс], пункт 13, об'єкти функції порівняння за замовчуванням std::less<Key>відповідно до 23.4.2 [Associative.map.syn] та 23.4. 3 [асоціативний.сет.син]. Згідно 20.10.5 [порівняння] пункту 4 загальний шаблон для std::less<...>нічого НЕ визначити вкладений тип , is_transparentале std::less<void>спеціалізація робить. Тобто, ні, ви не отримуєте прозорого оператора за замовчуванням.
Дітмар Кюль

У вас є ідеї щодо називання? Я маю на увазі чому is_transparent?
плазмацель

Хочете "конкретного прикладу, де це корисно"? Ось мій випадок використання
spraff

19

Далі наведено всі копії-пасти з n3657 .

В. Яка мета зробити компаратор "прозорим"?

A. Асоціативні функції пошуку контейнерів (find, lower_bound, верхній_bound, рівний_range) беруть аргумент лише key_type, вимагаючи від користувачів побудови (неявно або явно) об'єкта key_type для здійснення пошуку. Це може бути дорогим, наприклад, побудова великого об'єкта для пошуку в наборі, коли функція компаратора дивиться лише на одне поле об'єкта. Серед користувачів є бажання мати можливість здійснювати пошук, використовуючи інші типи, порівнянні з key_type.

В. Яку проблему це вирішує

A. У LWG були такі проблеми, як наступний:

std::set<std::string> s = /* ... */;
s.find("key");

У C ++ 11 це створить єдиний тимчасовий рядок std :: і потім порівняє його з елементами, щоб знайти ключ.

Зі зміною, запропонованою N3465, функція std :: set :: find () буде необмеженим шаблоном, який передасть const char * до функції компаратора, std :: less, яка побудує std :: string тимчасовий для кожне порівняння. LWG вважала цю проблему ефективністю серйозною проблемою. Функція find () шаблону також запобігає знаходженню NULL в контейнері покажчиків, що призводить до того, що раніше дійсний код більше не компілюється, але це розглядалося як менш серйозна проблема, ніж тиха регресія продуктивності

З: Чи це змінює спосіб роботи стандартних контейнерів

A. Ця пропозиція змінює асоціативні контейнери в і перевантажуючи функції члена пошуку шаблонами функцій члена. Мовних змін немає.

Q. так, набір за замовчуванням втрачає свої знаходження, підрахунок тощо

A. Практично на весь існуючий код C ++ 11 не впливає, оскільки функцій-членів немає, якщо нові функції бібліотеки C ++ 14 не використовуються як функції порівняння.

Щоб цитувати Yakk ,

У C ++ 14, std :: set :: find є функцією шаблону, якщо існує порівняння :: is_transparent. Тип, який ви передаєте, не повинен бути ключовим, а лише еквівалентним у порівнянні.

і n3657,

Додайте абзац 13 у 23.2.4 [Associative.reqmts]: Шаблони функцій учасників find, lower_bound, верхній_bound та enako_range не братимуть участь у вирішенні перевантажень, якщо тип Порівняти :: is_transparent не існує.

n3421 надає приклад "Прозорих функціонерів оператора" .

Повний код тут .


1
Є чи на std::set<std::string>самому справі вигоди від «проходження char const *через", або вам потрібно зробити std::set<std::string, std::less<>>?
Керрек СБ

@Kerrek Я думаю, що "передача статусу *" була проблемою, яку вони намагалися уникати, якщо я не помиляюся. Подивіться на формулювання:With the change proposed by N3465 the std::set::find() function would be an unconstrained template which would pass the const char* through to the comparator function, std::less<std::string>, which would construct a std::string temporary for every comparison. The LWG considered this performance problem to be a serious issue.

Ваша цитата та мія пункту 13 говорять про зворотне: "якщо тип не існує / не існує" ...?!
Керрек СБ

4
@ KerkerSB, це моя вина, N3657 повинен був сказати "існує", але я написав "не існує" ... це був пізній документ, написаний в останню хвилину. Проект стандарту правильний.
Джонатан Вейклі

3
Так, може бути зрозуміліше цитувати те, що я мав на увазі сказати не те, що я насправді сказав тоді :)
Джонатан Уейклі

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.