La dernière version de la norme c ++ introduit les soi-disant "objets de point de personnalisation" ( [customization.point.object] ), qui sont largement utilisés par la bibliothèque de plages.
Je semble comprendre qu'ils fournissent un moyen d'écrire une version personnalisée de begin
, swap
, data
, etc., qui se trouvent dans la bibliothèque standard d'ADL. Est-ce exact?
En quoi est-ce différent de la pratique précédente où un utilisateur définit une surcharge pour par exemple begin
pour son type dans son propre espace de noms? En particulier, pourquoi sont-ils des objets ?
Que sont les objets de point de personnalisation?
Ce sont des instances d'objet fonction dans l'espace de noms std
qui remplissent deux objectifs: premier déclencheur inconditionnel (conceptifié) des exigences de type sur les arguments , puis envoi vers la fonction correcte dans l'espace de noms std
ou via ADL.
En particulier, pourquoi sont-ils des objets ?
Cela est nécessaire pour contourner une deuxième phase de recherche qui apporterait directement la fonction fournie par l'utilisateur via ADL (cela devrait être reporté par conception). Voir ci-dessous pour plus de détails.
... et comment les utiliser?
Lors du développement d'une application: ce n'est généralement pas le cas. Il s'agit d'une fonctionnalité de bibliothèque standard, elle ajoutera une vérification de concept aux futurs points de personnalisation, résultant, espérons-le, par exemple. dans des messages d'erreur clairs lorsque vous gâchez les instanciations du modèle. Cependant, avec un appel qualifié à un tel point de personnalisation, vous pouvez l'utiliser directement. Voici un exemple avec un objet std::customization_point
Imaginaire qui adhère à la conception:
namespace a {
struct A {};
// Knows what to do with the argument, but doesn't check type requirements:
void customization_point(const A&);
}
// Does concept checking, then calls a::customization_point via ADL:
std::customization_point(a::A{});
Ce n'est actuellement pas possible avec par exemple std::swap
, std::begin
Et similaires.
Permettez-moi d'essayer de digérer la proposition derrière cette section dans la norme. Il existe deux problèmes avec les points de personnalisation "classiques" utilisés par la bibliothèque standard.
Ils sont faciles à se tromper. Par exemple, l'échange d'objets dans du code générique est censé ressembler à ceci
template<class T> void f(T& t1, T& t2)
{
using std::swap;
swap(t1, t2);
}
mais faire un appel qualifié à std::swap(t1, t2)
à la place est trop simple - le swap
fourni par l'utilisateur ne sera jamais appelé (voir N4381 , Motivation et portée)
Plus sévèrement, il n'y a aucun moyen de centraliser (conceptualisé) les contraintes sur les types passés à de telles fonctions fournies par l'utilisateur (c'est aussi pourquoi ce sujet a gagné en importance avec C++ 20). De nouveau de N4381 :
Supposons qu'une future version de
std::begin
Nécessite que son argument modélise un concept Range. L'ajout d'une telle contrainte n'aurait aucun effet sur le code utilisant idiomatiquementstd::begin
:using std::begin;
begin(a);
Si l'appel pour commencer est envoyé vers une surcharge définie par l'utilisateur, la contrainte surstd::begin
A été contournée.
La solution décrite dans la proposition atténue les deux problèmes par une approche comme la mise en œuvre imaginaire suivante de std::begin
.
namespace std {
namespace __detail {
/* Classical definitions of function templates "begin" for
raw arrays and ranges... */
struct __begin_fn {
/* Call operator template that performs concept checking and
* invokes begin(arg). This is the heart of the technique.
* Everyting from above is already in the __detail scope, but
* ADL is triggered, too. */
};
}
/* Thanks to @cpplearner for pointing out that the global
function object will be an inline variable: */
inline constexpr __detail::__begin_fn begin{};
}
Tout d'abord, un appel qualifié pour, par exemple, std::begin(someObject)
dévie toujours via std::__detail::__begin_fn
, ce qui est souhaité. Pour ce qui se passe avec un appel sans réserve, je me réfère à nouveau à l'article original:
Dans le cas où commencer est appelé non qualifié après avoir mis
std::begin
Dans la portée, la situation est différente. Dans la première phase de recherche, le nom begin sera résolu en l'objet globalstd::begin
. Étant donné que la recherche a trouvé un objet et non une fonction, la deuxième phase de la recherche n'est pas effectuée. En d'autres termes, sistd::begin
Est un objet, alorsusing std::begin; begin(a);
est équivalent àstd::begin(a);
qui, comme nous l'avons déjà vu, effectue une recherche dépendant des arguments sur les utilisateurs 'nom.
De cette façon, la vérification de concept peut être effectuée au sein de l'objet fonction dans l'espace de noms std
, avant l'appel ADL à une fonction fournie par l'utilisateur est effectuée. Il n'y a aucun moyen de contourner cela.
"Objet de point de personnalisation" est un peu inapproprié. Beaucoup - probablement une majorité - ne sont pas réellement des points de personnalisation.
Des choses comme ranges::begin
, ranges::end
, et ranges::swap
sont des "vrais" CPO. L'appel de l'une d'entre elles entraîne une métaprogrammation complexe pour déterminer s'il existe un begin
ou end
ou swap
personnalisé valide à appeler, ou si l'implémentation par défaut doit être utilisé, ou si l'appel doit être mal formé (d'une manière compatible SFINAE). Étant donné qu'un certain nombre de concepts de bibliothèque sont définis en termes d'appels CPO valides (comme Range
et Swappable
), le code générique correctement contraint doit utiliser ces CPO. Bien sûr, si vous connaissez le type de béton et une autre façon d'en retirer un itérateur, n'hésitez pas.
Des choses comme ranges::cbegin
sont des CPO sans la partie "CP". Ils font toujours la chose par défaut, donc ce n'est pas vraiment un point de personnalisation. De même, les objets d'adaptateur de plage sont des CPO mais il n'y a rien de personnalisable à leur sujet. Les classer comme CPO est plus une question de cohérence (pour cbegin
) ou de commodité de spécification (adaptateurs).
Enfin, des choses comme ranges::all_of
sont des quasi-CPO ou niebloids. Ils sont spécifiés en tant que modèles de fonction avec des propriétés de blocage ADL magiques spéciales et une formulation de belette pour leur permettre d'être implémentés en tant qu'objets fonction à la place. Il s'agit principalement d'empêcher ADL de récupérer la surcharge non contrainte dans l'espace de noms std
lorsqu'un algorithme contraint dans std::ranges
est appelé non qualifié. Parce que le std::ranges
l'algorithme accepte les paires itérateur-sentinelle, il est généralement moins spécialisé que son homologue std
et perd ainsi la résolution de surcharge.