Que sont les comparateurs transparents?

106

En C ++ 14, les conteneurs associatifs semblent avoir changé depuis C ++ 11 - [associative.reqmts] / 13 dit:

Les modèles de fonction membre find, count, lower_bound, upper_boundet equal_rangene participent pas à la résolution de surcharge sauf si le type Compare::is_transparentexiste.

Quel est le but de rendre un comparateur "transparent"?

C ++ 14 fournit également des modèles de bibliothèques comme celui-ci:

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;
};

Ainsi, par exemple, std::set<T, std::less<T>>n'aurait pas de comparateur transparent, mais en std::set<T, std::less<>> aurait un.

Quel problème cela résout-il et cela change-t-il le fonctionnement des conteneurs standard? Par exemple, les paramètres du modèle de std::setsont encore Key, Compare = std::less<Key>, ..., plus le jeu par défaut perdre ses find, count, des membres?

Kerrek SB
la source
Par exemple, consultez cette description de cppreference . Et je me sens stupide maintenant, parce que je note le mot " modèle de fonction membre " ...
Kerrek SB
cppreference a également un texte
Cubbi

Réponses:

60

Quel problème cela résout-il,

Voir la réponse de Dietmar et la réponse de remyabel .

et cela change-t-il le fonctionnement des conteneurs standard?

Non, pas par défaut.

Les nouvelles surcharges de modèle de fonction membre de findetc. vous permettent d'utiliser un type comparable à la clé du conteneur, au lieu d'utiliser le type de clé lui-même. Voir N3465 par Joaquín Mª López Muñoz pour la justification et une proposition détaillée et soigneusement écrite pour ajouter cette fonctionnalité.

Lors de la réunion de Bristol, le LWG a convenu que la fonction de recherche hétérogène était utile et souhaitable, mais nous ne pouvions pas être sûrs que la proposition de Joaquín serait sûre dans tous les cas. La proposition N3465 aurait causé de sérieux problèmes pour certains programmes (voir la section Impact sur le code existant ). Joaquín a préparé un projet de proposition mis à jour avec des implémentations alternatives avec différents compromis, ce qui était très utile pour aider le LWG à comprendre les avantages et les inconvénients, mais ils risquaient tous de casser certains programmes d'une manière ou d'une autre, il n'y avait donc pas de consensus pour ajouter la fonctionnalité. Nous avons décidé que même s'il ne serait pas sûr d'ajouter la fonctionnalité sans condition, ce serait sûr si elle était désactivée par défaut et uniquement "opt-in".

La principale différence de la proposition N3657 (qui était une révision de dernière minute par moi-même et STL basée sur N3465 et un projet ultérieur non publié par Joaquín) était d'ajouter le is_transparenttype comme protocole qui peut être utilisé pour opter pour la nouvelle fonctionnalité.

Si vous n'utilisez pas de "foncteur transparent" (c'est-à-dire celui qui définit un is_transparenttype) alors les conteneurs se comportent comme ils l'ont toujours fait, et c'est toujours la valeur par défaut.

Si vous choisissez d'utiliser std::less<>(ce qui est nouveau pour C ++ 14) ou un autre type de "foncteur transparent", vous obtenez la nouvelle fonctionnalité.

L'utilisation std::less<>est facile avec les modèles d'alias:

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

Le nom is_transparentvient du N3421 de STL qui a ajouté les "opérateurs diamantés" au C ++ 14. Un "foncteur transparent" est celui qui accepte tous les types d'arguments (qui ne doivent pas nécessairement être identiques) et transmet simplement ces arguments à un autre opérateur. Un tel foncteur se trouve être exactement ce que vous voulez pour une recherche hétérogène dans des conteneurs associatifs, de sorte que le type a is_transparentété ajouté à tous les opérateurs de diamant et utilisé comme type de balise pour indiquer que la nouvelle fonctionnalité doit être activée dans les conteneurs associatifs. Techniquement, les conteneurs n'ont pas besoin d'un "foncteur transparent", juste un qui prend en charge l'appeler avec des types hétérogènes (par exemple, le pointer_comptype dans https://stackoverflow.com/a/18940595/981959 n'est pas transparent selon la définition de STL,pointer_comp::is_transparentpermet de l'utiliser pour résoudre le problème). Si vous ne jamais votre recherche dans std::set<T, C>des clés de type Tou intalors Cbesoin que d'être appelable avec des arguments de type Tet int(dans un ordre), il n'a pas besoin d'être vraiment transparent. Nous avons utilisé ce nom en partie parce que nous ne pouvions pas trouver un meilleur nom (j'aurais préféré is_polymorphicparce que ces foncteurs utilisent le polymorphisme statique, mais il existe déjà un std::is_polymorphictrait de type qui fait référence au polymorphisme dynamique).

Jonathan Wakely
la source
3
Hé, êtes-vous celui à qui STL a dit: "Bien sûr, vous pouvez faire une déduction d'argument modèle dans votre tête" dans le discours Woolstar lié?
Kerrek SB
10
Non, je n'étais pas là, mais il y a des gens avec beaucoup plus de compilateurs conformes que moi :)
Jonathan Wakely
Je suppose que "opérateur diamant" fait référence <>dans la proposition liée, mais cette proposition n'a pas été introduite <>- c'est la syntaxe existante pour une liste de paramètres de modèle vide. Les "foncteurs d'opérateur diamant" seraient un peu moins déroutants.
Qwertie
33

En C ++ 11 il n'y a pas des modèles de membres find(), lower_bound()etc. C'est, rien ne se perd par ce changement. Les modèles de membre ont été introduits avec n3657 pour permettre l'utilisation de clés hétérogènes avec les conteneurs associatifs. Je ne vois aucun exemple concret où cela soit utile à l'exception de l'exemple qui est bon et mauvais!

L' is_transparentutilisation est destinée à éviter les conversions indésirables. Si les modèles de membre n'étaient pas soumis à des contraintes, le code existant peut passer directement par des objets qui auraient été convertis sans les modèles de membre. L'exemple de cas d'utilisation de n3657 consiste à localiser un objet dans un en std::set<std::string>utilisant un littéral de chaîne: avec la définition C ++ 11, un std::stringobjet est construit lors du passage d'un littéral de chaîne à la fonction membre correspondante. Avec la modification, il est possible d'utiliser directement la chaîne littérale. Si l'objet de la fonction de comparaison sous-jacent est implémenté exclusivement en termes de std::stringcela est mauvais car maintenant un std::stringserait créé pour chaque comparaison. D'autre part, si l'objet de la fonction de comparaison sous-jacent peut prendre unstd::string et une chaîne littérale, qui peut éviter la construction d'un objet temporaire.

Le is_transparenttype imbriqué dans l'objet fonction de comparaison fournit un moyen de spécifier si la fonction membre basée sur un modèle doit être utilisée: si l'objet fonction de comparaison peut traiter des arguments hétérogènes, il définit ce type pour indiquer qu'il peut traiter efficacement différents arguments. Par exemple, les nouveaux objets de fonction d'opérateur délèguent simplement operator<()et prétendent être transparents. Cela, au moins, fonctionne pour std::stringqui a surchargé moins que les opérateurs prenant char const*comme argument. Étant donné que ces objets de fonction sont également nouveaux, même s'ils ne font pas la bonne chose (c'est-à-dire nécessitent une conversion pour un certain type), il ne s'agirait au moins pas d'un changement silencieux entraînant une dégradation des performances.

Dietmar Kühl
la source
Merci - voir mon commentaire sur l'autre question: obtenez-vous le comportement transparent par défaut?
Kerrek SB
8
@KerrekSB: le comportement transparent est activé lorsque is_transparentest défini dans l'objet fonction de comparaison selon 23.2.4 [associative.reqmts] paragraphe 13. Les objets fonction de comparaison par défaut sont std::less<Key>conformes à 23.4.2 [associative.map.syn] et 23.4. 3 [associative.set.syn]. Selon 20.10.5 [comparaison] paragraphe 4, le modèle général pour std::less<...>ne définit pas un type imbriqué is_transparentmais la std::less<void>spécialisation le fait. Autrement dit, non, vous n'obtenez pas d'opérateur transparent par défaut.
Dietmar Kühl
Avez-vous une idée de la dénomination? Je veux dire pourquoi is_transparent?
plasmacel
Vous voulez un "exemple concret où cela est utile"? Voici mon cas d'utilisation
spraff
19

Ce qui suit est toutes les pâtes à copier de n3657 .

Q. Quel est le but de rendre un comparateur "transparent"?

A. Les fonctions de recherche de conteneur associatives (find, lower_bound, upper_bound, equal_range) prennent uniquement un argument de key_type, obligeant les utilisateurs à construire (implicitement ou explicitement) un objet de key_type pour effectuer la recherche. Cela peut être coûteux, par exemple la construction d'un grand objet à rechercher dans un ensemble lorsque la fonction de comparaison ne regarde qu'un seul champ de l'objet. Les utilisateurs souhaitent vivement pouvoir effectuer des recherches en utilisant d'autres types comparables à key_type.

Q. Quel problème cela résout-il

A. Le LWG avait des préoccupations au sujet du code comme suit:

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

En C ++ 11, cela construira une seule std :: string temporaire, puis la comparera avec des éléments pour trouver la clé.

Avec le changement proposé par N3465, la fonction std :: set :: find () serait un modèle sans contrainte qui passerait le const char * à la fonction de comparateur, std :: less, qui construirait un std :: string temporaire pour chaque comparaison. Le LWG a considéré ce problème de performance comme un problème sérieux. La fonction template find () empêcherait également de trouver NULL dans un conteneur de pointeurs, ce qui empêche le code précédemment valide de se compiler, mais cela a été considéré comme un problème moins sérieux que la régression silencieuse des performances.

Est -ce que cela change le fonctionnement des conteneurs standard

A. Cette proposition modifie les conteneurs associatifs dans et en surchargeant les fonctions membres de recherche avec des modèles de fonctions membres. Il n'y a pas de changement de langue.

De même, l'ensemble par défaut perd ses membres find, count, etc.

R. Presque tout le code C ++ 11 existant n'est pas affecté car les fonctions membres ne sont pas présentes à moins que les nouvelles fonctionnalités de la bibliothèque C ++ 14 soient utilisées comme fonctions de comparaison.

Pour citer Yakk ,

En C ++ 14, std :: set :: find est une fonction modèle si Compare :: is_transparent existe. Le type que vous passez n'a pas besoin d'être Key, juste équivalent sous votre comparateur.

et n3657,

Ajouter le paragraphe 13 dans 23.2.4 [associative.reqmts]: Les modèles de fonction membre find, lower_bound, upper_bound et equal_range ne doivent pas participer à la résolution de surcharge à moins que le type Compare :: is_transparent n'existe pas .

n3421 fournit un exemple de «fonctions d'opérateur transparentes» .

Le code complet est ici .

Communauté
la source
1
Bénéficie-t-il std::set<std::string>réellement du "passage char const *", ou avez-vous besoin de faire un std::set<std::string, std::less<>>?
Kerrek SB
@Kerrek Je pense que "passer le char const *" était le problème qu'ils essayaient d'éviter, si je ne me trompe pas. Regardez le libellé: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.
Votre citation et la mienne du paragraphe 13 disent le contraire: "sauf si le type existe / n'existe pas" ...?!
Kerrek SB
4
@KerrekSB, c'est ma faute, N3657 était censé dire "existe" mais j'ai écrit "n'existe pas" ... c'était un article tardif écrit à la dernière minute. Le projet de norme est correct.
Jonathan Wakely
3
Oui, il serait peut-être plus clair de citer ce que je voulais dire et non ce que j'ai réellement dit à l'époque :)
Jonathan Wakely
7

Stephan T Lavavej parle des problèmes où le compilateur continue de créer des temporaires, et comment sa proposition de foncteurs d'opérateurs transparents résoudra cela en c ++ 1y

GoingNative 2013 - N'aidez pas le compilateur (à peu près à l'heure)

Woolstar
la source