Что такое прозрачные компараторы?

В С++ 14 ассоциативные контейнеры, похоже, изменились с С++ 11 – [associative.reqmts]/13 говорит:

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

Какова цель создания "прозрачного" компаратора?

С++ 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 и т.д.?

Ответ 1

  Какую проблему это решает,

См. ответ Dietmar и ответ remyabel.

и это меняет работу стандартных контейнеров?

Нет, не по умолчанию.

Перегрузки шаблона функции-члена find и т.д. Позволяют использовать тип, сопоставимый с ключом контейнера, вместо использования самого типа ключа. См. N3465 Хоакина Мопа Лопеса Муньоса для обоснования и подробного, тщательно написанного предложения по добавлению этой функции.

На встрече в Бристоле LWG согласилась с тем, что функция гетерогенного поиска была полезной и желательной, но мы не могли быть уверены, что предложение Хоакина будет безопасным во всех случаях. Предложение N3465 вызвало бы серьезные проблемы для некоторых программ (см. раздел Влияние на существующий код). Хоакин подготовил обновленный проект предложения с некоторыми альтернативными реализациями с различными компромиссами, который был очень полезен, помогая LWG понять плюсы и минусы, но все они рисковали каким-то образом нарушить некоторые программы, поэтому не было единого мнения по поводу добавления этой функции. Мы решили, что хотя добавлять эту функцию безоговорочно небезопасно, было бы безопасно, если бы она была отключена по умолчанию и включала только "согласие".

Ключевым отличием предложения N3657 (которое было написано мной и STL в последний момент на основе N3465 и более поздним неопубликованным черновиком Хоакина) было добавление типа is_transparent в качестве протокола, который можно использовать для включения новых функций.

Если вы не используете "прозрачный функтор" (т.е. тот, который определяет тип is_transparent), тогда контейнеры ведут себя так же, как и всегда, и это по умолчанию.

Если вы решите использовать std::less<> (который является новым для С++ 14) или другой тип "прозрачного функтора", тогда вы получите новую функциональность.

Использовать std::less<> легко с шаблонами псевдонимов:

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

Название is_transparent происходит от STL N3421, который добавил "операторы алмазов" в С++ 14. "Прозрачный функтор" - это тот, который принимает любые типы аргументов (которые не обязательно должны быть одинаковыми) и просто перенаправляет эти аргументы другому оператору. Такой функтор оказывается именно тем, что вам нужно для гетерогенного поиска в ассоциативных контейнерах, поэтому тип is_transparent был добавлен ко всем операторам diamond и использован в качестве типа тега, чтобы указать, что новая функция должна быть включена в ассоциативных контейнерах. Технически, контейнерам не нужен "прозрачный функтор", только тот, который поддерживает вызов его с гетерогенными типами (например, тип pointer_comp в fooobar.com/questions/25232/... не прозрачен в соответствии с определением STL, но определение pointer_comp::is_transparent позволяет ему быть использованы для решения проблемы). Если вы когда-либо просматривали ваш std::set<T, C> только с ключами типа T или int, тогда C нужно будет вызывать только с аргументами типа T и int (в любом порядке), это не должен быть действительно прозрачным. Мы использовали это имя отчасти потому, что не могли придумать более подходящее имя (я бы предпочел is_polymorphic, потому что такие функторы используют статический полиморфизм, но уже есть черта типа std::is_polymorphic, которая относится к динамическому полиморфизму).

Ответ 2

В С++ 11 нет шаблонов участников find(), lower_bound() и т.д. То есть ничто не теряется при этом изменении. Шаблоны-члены были введены с n3657, чтобы использовать гетерогенные ключи с ассоциативными контейнерами. Я не вижу конкретного примера, где это полезно, кроме примера, который хорош и плох!

Использование is_transparent предназначено для предотвращения нежелательных преобразований. Если шаблоны-члены не были ограничены, существующий код может проходить через объекты, которые были бы преобразованы без шаблонов-членов. Пример использования case из n3657 - это поиск объекта в std::set<std::string> с использованием строкового литерала: при определении С++ 11 объект std::string создается при передаче строковых литералов в соответствующую функцию-член. С изменением можно напрямую использовать строковый литерал. Если основной объект функции сравнения реализуется исключительно в терминах std::string, что плохо, потому что теперь для каждого сравнения будет создан std::string. С другой стороны, если базовый объект функции сравнения может принимать std::string и строковый литерал, это может предотвратить создание временного объекта.

Вложенный тип is_transparent в объекте функции сравнения предоставляет способ указать, следует ли использовать функцию шаблона-члена: если объект функции сравнения может иметь дело с разнородными аргументами, он определяет этот тип, чтобы указать, что он может иметь дело с различные аргументы эффективно. Например, новые объекты операторской функции просто делегируются на operator<() и утверждают, что они прозрачны. Это, по крайней мере, работает для std::string, который перегружен меньше, чем операторы, принимающие char const* в качестве аргумента. Поскольку эти функциональные объекты также новы, даже если они делают неправильную вещь (т.е. Требуют преобразования для некоторого типа), это, по крайней мере, не будет молчащим изменением, приводящим к ухудшению производительности.

Ответ 3

Ниже приведена копия-паста из n3657.

В. Какова цель сделать компаратор "прозрачным"?

A. Функции поиска ассоциативных контейнеров (find, lower_bound, upper_bound, equal_range) принимает только аргумент key_type, требующий пользователи, чтобы построить (неявно или явно) объект key_type для поиска. Это может быть дорого, например построение большой объект для поиска в наборе, когда функция сравнения только смотрит на одно поле объекта. У пользователей есть сильное желание иметь возможность поиска с использованием других типов, которые сопоставимы с key_type.

В. Какую проблему это решает

О. У LWG были проблемы с кодом, подобным следующему:

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

В С++ 11 это создаст одну временную строку std::string, а затем сравните его с элементами, чтобы найти ключ.

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

Q. это меняет работу стандартных контейнеров

A. Это предложение изменяет ассоциативные контейнеры в и перегружая функции-члены поиска функцией-членом шаблоны. Там нет языковых изменений.

В. Так что набор по умолчанию теряет своих членов find, count и т.д.

О. Почти весь существующий код С++ 11 не затронут, потому что член функции отсутствуют, если не используются новые функции библиотеки С++ 14 в качестве функции сравнения.

Цитировать Якк,

В С++ 14 std::set :: find является функцией шаблона, если Compare :: is_transparent существует. Тип, который вы передаете, не требуется быть ключевым, просто эквивалентным под вашим компаратором.

и n3657,

Добавить пункт 13 в 23.2.4 [associative.reqmts]: Шаблоны функций-членов find, lower_bound, upper_bound и equal_range не должен участвовать в разрешении перегрузки, если только Тип Compare :: is_transparent не существует существует.

n3421 предоставляет пример "прозрачных операторных функторов".

полный код здесь.

Ответ 4

Стефан Т Лававей рассказывает о проблемах, когда компилятор продолжает создавать временные объекты, и о том, как его предложение прозрачных функторов операторов решит эту проблему в c ++ 1y

GoingNative 2013 - Не помогите компилятору (примерно на часе)