Quel est le but de C ++ 20 std :: common_reference?

Réponses:

46

common_reference est né de mes efforts pour proposer une conceptualisation des itérateurs de STL qui prend en charge les itérateurs proxy.

Dans la STL, les itérateurs ont deux types associés d'intérêt particulier: referenceet value_type. Le premier est le type de retour de l'itérateur operator*et le type value_type(non const, non-reference) des éléments de la séquence.

Les algorithmes génériques ont souvent besoin de faire des choses comme ceci:

value_type tmp = *it;

... donc nous savons qu'il doit y avoir une relation entre ces deux types. Pour les itérateurs non proxy, la relation est simple: elle referenceest toujours value_type, éventuellement const et qualifiée de référence. Les premières tentatives de définition du InputIteratorconcept ont exigé que l'expression *itsoit convertible const value_type &et, pour la plupart des itérateurs intéressants, cela suffit.

Je voulais que les itérateurs en C ++ 20 soient plus puissants que cela. Par exemple, considérez les besoins d'un zip_iteratorqui itère deux séquences en étape de verrouillage. Lorsque vous déréférencez a zip_iterator, vous obtenez un temporaire pairdes deux referencetypes d' itérateurs . Ainsi, zip'ing a vector<int>et a vector<double>auraient ces types associés:

zipitérateur reference: pair<int &, double &>
zipitérateur value_type:pair<int, double>

Comme vous pouvez le voir, ces deux types ne sont pas liés l'un à l'autre simplement en ajoutant une qualification CV et REF de niveau supérieur. Et pourtant, laisser les deux types être arbitrairement différents se sent mal. Il y a clairement une relation ici. Mais quelle est la relation et que peuvent supposer en toute sécurité les algorithmes génériques qui opèrent sur les itérateurs au sujet des deux types?

La réponse en C ++ 20 est que pour tout type d'itérateur valide, proxy ou non, les types reference &&et value_type &partagent une référence commune . En d'autres termes, pour certains itérateurs, itil existe un type CRqui rend les éléments suivants bien formés:

void foo(CR) // CR is the common reference for iterator I
{}

void algo( I it, iter_value_t<I> val )
{
  foo(val); // OK, lvalue to value_type convertible to CR
  foo(*it); // OK, reference convertible to CR
}

CRest la référence commune. Tous les algorithmes peuvent s'appuyer sur le fait que ce type existe et peuvent être utilisés std::common_referencepour le calculer.

C'est donc le rôle que common_referencejoue la STL en C ++ 20. Généralement, sauf si vous écrivez des algorithmes génériques ou des itérateurs proxy, vous pouvez l'ignorer en toute sécurité. Il est là sous les couvertures garantissant que vos itérateurs respectent leurs obligations contractuelles.


EDIT: Le PO a également demandé un exemple. C'est un peu artificiel, mais imaginez qu'il s'agit de C ++ 20 et que vous disposez d'une plage rde type Rà accès aléatoire dont vous ne savez rien et que vous souhaitez accéder à sortla plage.

Imaginez en outre que pour une raison quelconque, vous souhaitez utiliser une fonction de comparaison monomorphe, comme std::less<T>. (Peut-être avez-vous effacé la plage et vous devez également effacer la fonction de comparaison et la passer par un virtual? Encore une fois.) Que devrait Tcontenir std::less<T>? Pour cela, vous utiliseriez common_reference, ou l'aide iter_common_reference_tqui est implémentée en termes de celui-ci.

using CR = std::iter_common_reference_t<std::ranges::iterator_t<R>>;
std::ranges::sort(r, std::less<CR>{});

Cela est garanti de fonctionner, même si la plage ra des itérateurs proxy.

Eric Niebler
la source
2
Peut-être que je suis dense, mais pouvez-vous préciser quelle est la référence commune dans l'exemple de paire de zip?
happydave
4
Idéalement, pair<T&,U&>et pair<T,U>&aurait une référence commune, et ce serait simplement pair<T&,U&>. Cependant, pour std::pair, il n'y a pas de conversion de pair<T,U>&à pair<T&,U&>même si une telle conversion est saine en principe. (C'est d'ailleurs la raison pour laquelle nous n'avons pas de zipvue en C ++ 20.)
Eric Niebler
4
@EricNiebler: " C'est d'ailleurs pour cette raison que nous n'avons pas de vue zip en C ++ 20. " Y a-t-il une raison pour laquelle un itérateur zip devrait utiliser pair, au lieu d'un type qui pourrait être spécifiquement conçu pour son usage , avec des conversions implicites appropriées selon les besoins?
Nicol Bolas
5
@Nicol Bolas Il n'est pas nécessaire d'utiliser std::pair; tout type de paire approprié avec les conversions appropriées fera l'affaire, et range-v3 définit un tel type de paire. Au sein du comité, le LEWG n'aimait pas l'idée d'ajouter à la bibliothèque standard un type qui était presque mais pas tout à fait std::pair, qu'il soit normatif ou non, sans d'abord faire preuve de diligence raisonnable sur les avantages et les inconvénients de simplement faire du std::pairtravail.
Eric Niebler
3
tuple, pair, tomato, to- MAH- to. paira cette fonctionnalité intéressante que vous pouvez accéder aux éléments avec .firstet .second. Les liaisons structurées contribuent à certaines des maladresses de travailler avec tuples, mais pas toutes.
Eric Niebler