8
votes

Désactiver la spécialisation de classe à l'aide de concepts

J'implémente ma propre version de std :: span en utilisant Concepts TS. Je suis resté bloqué en implémentant ces constructeurs :

template<class Container> constexpr span(Container& cont);
template<class Container> constexpr span(const Container& cont);

Remarques: Ces constructeurs ne doivent pas participer à la résolution de surcharge sauf si:

  • Container n'est pas une spécialisation de span , et
  • Container n'est pas une spécialisation de array

Comment implémenter cela en utilisant des concepts?


2 commentaires

Pouvez-vous implémenter un modèle de traits qui répond à ces questions? Pouvez-vous convertir un modèle de traits en un concept? Est-ce que cela résout votre problème?


Pourrait avoir des réponses ici ? Bonne question!


3 Réponses :


2
votes

Vous pouvez utiliser des traits de type pour vérifier si un type est une spécialisation de span ou std :: array . Cela fonctionne pour moi:

#include <type_traits>

template<typename, std::ptrdiff_t> class span;

template <typename T>
struct is_array : std::false_type { };
template <typename T, size_t N>
struct is_array<std::array<T, N>> : std::true_type { };

template <typename T>
struct is_span : std::false_type { };
template <typename T, std::ptrdiff_t P>
struct is_span<span<T, P>> : std::true_type { };

template <typename T>
concept bool NotSpanNotArray = !is_array<T>::value && !is_span<T>::value;

template<typename, std::ptrdiff_t> class span {
public:
  template<NotSpanNotArray T> constexpr span(T& cont);
  // template<NotSpanNotArray T> constexpr span(const T& cont);
};

Démo de travail: https://wandbox.org / permlink / M0n60U8Hl4mpacuI

Je ne suis pas sûr à 100% si une telle solution répond à cette participation à la résolution de surcharge si et seulement si l'exigence. Certains juristes linguistiques pourraient clarifier cela.


MISE À JOUR

Je viens de réaliser que std :: is_array fonctionne uniquement pour les tableaux "ordinaires", pas std :: array . Par conséquent, j'ai également ajouté un trait de type is_array personnalisé.


5 commentaires

"Si et seulement si" a été ajouté par un autre utilisateur, j'ai rétabli le libellé officiel qui est "ne pas à moins que".


La résolution des surcharges n'est-elle pas faite en enveloppant ceci dans un enable_if pour utiliser SFINAE?


@Lyberta Désolé, j'essayais de me débarrasser du triple négatif. Je pense que la forme correcte aurait été «seulement si», mais je crois que l'intention était claire malgré tout.


@davidbak pas avec des concepts, c'est la bonne syntaxe mais récupère la contrainte à l'envers (doit être NotSpanNorArray )


@Barry mis à jour, merci. Je suis maintenant juste sur mon téléphone, je mettrai également à jour la démo.



2
votes

Commencez par créer un trait pour vérifier les spécialisations. array et span se ressemblent en ce sens qu'ils prennent un paramètre de type et un paramètre non-type:

template <typename Element, std::ptrdiff_t Extent = -1>
struct span {
    template <AllowedContainer<E> C>
    span(C&&);
};

Et puis nous peut construire un concept à partir de cela:

template <typename T, typename E>
concept ConstAllowedContainer = AllowedContainer<T const, E>;

template <typename Element, std::ptrdiff_t Extent = -1>
struct span {
    template <AllowedContainer<E> C>      span(C&);
    template <ConstAllowedContainer<E> C> span(C const&);
};

Que vous utiliseriez comme:

template <typename Element, std::ptrdiff_t Extent = -1>
struct span {
    template <typename C> requires AllowedContainer<C, Element>
    span(C&);
    template <typename C> requires AllowedContainer<C const, Element>
    span(C const&);
};

Le const -ness -ness là-bas empêche la belle syntaxe partial-concept-id , mais nous pourrions simplement ajouter un autre concept pour cela, je suppose:

// the last bullet point
template <typename T, typename E>
concept ValidForElement =
    ConvertibleTo<std::remove_pointer_t<T>(*)[], E(*)[]>;

template <typename T, typename E>
concept AllowedContainer =
    // not a specialization of span (note: requires forward declaration of span)
    !is_specialization_v<std::remove_cv_t<T>, std::span>
    // not a specialization of array
    && !is_specialization_v<std::remove_cv_t<T>, std::array>
    // not a raw array
    && !std::is_array_v<std::remove_cv_t<T>>
    && requires (T cont) {
        // data(cont) is well-formed and has a valid type
        { data(cont); } -> ValidForElement<E>
        // size(cont) is well-formed
        { size(cont); }
    };


2 commentaires

Est-il possible de généraliser is_specialization en utilisant des variadiques?


@Lyberta Le problème est que vous avez différents types de paramètres de modèle: un paramètre de type et un paramètre non-type. Il n'y a aucun type que vous pourriez mettre là-dedans. C'est exactement le cas où un paramètre "anykind" serait super utile.



0
votes

Vous utilisez mal les concepts. Dans un concept, le constructeur d'envergure mondiale ressemblera à ceci avec la syntaxe concept-ts:

struct span{
   span(const ContiguousRange auto&);
   span(ContiguousRange auto&);
   span(const span&) =default;
   span(span&) =default;
   };

ou avec c ++ 20:

struct span{
   span(const ContiguousRange&);
   span(ContiguousRange&);
   span(const span&) =default;
   span(span&) =default;
   };

Les concepts sont là pour faciliter l'abstraction. Ainsi, si votre interface devient plus complexe lors de l'utilisation de concepts, vous avez raté l'abstraction. L'abstraction ici est ContiguousRange (merci à @Lyberta).


9 commentaires

span nécessite une mémoire contiguë. AFAIK Range ne le garantit pas.


@Lyberta merci donc je corrige et utilise ContiguousRange à la place! eel.is/c++draft/range.req#range. raffinements-2


Eh bien, sur un point pratique, cela nécessiterait la mise en œuvre de plages, ce qui représente une énorme quantité de travail. Je n'implémente mon span que car il n'est pas encore dans la libstdc ++ que j'utilise. Peut-être que l'utilisation de plages est une meilleure interface, alors elle devrait être proposée pour la standardisation.


@Lyberta Dans ce cas, la vérification de concept que vous essayez d'implémenter sera inutile. La raison en est que span liste de constructeurs comme décrit dans fr .cppreference.com / w / cpp / container / span / span propose déjà des surcharges pour la spécialisation des tableaux et des span. La mise en œuvre de la vérification de concept sera presque inutile. Je dis presque parce que je suppose qu'il s'agit d'une erreur de conception ou de référence cpp dans cette liste. Ajoutez simplement un constructeur span (span &) et un constructeur span (span &) et le travail sera terminé.


J'ai implémenté les 10 constructeurs qui sont dans la norme.


Sauf que span a ce concept d'extension dynamique / statique supplémentaire, il doit donc traiter les plages contiguës qui ont des étendues statiques séparément


@Barry Je mettrais ces constructeurs à leur place: la classe spécialisée pour les extensions dynamiques ou statiques qui contiennent des données non statiques, mais c'est un détail.


@Lyberta Si vous implémentez la vérification de concept, vous ne suivrez pas la norme, ... alors pourquoi ne pas prendre la voie rapide.


@Lyberta Parce que la vérification de concept n'exclura span (container &) que lorsque l'argument est un span<...> donc il ne sert à rien d'implémenter cette énorme machine juste pour cette. Vous définissez un span (span <...> a &): span (as_const (a)) {} et le travail est terminé.