218 votes

Modèles C ++ n'acceptant que certains types

En Java, vous pouvez définir une classe générique qui accepte uniquement les types qui étendent la classe de votre choix, par exemple:

 public class ObservableList<T extends List> {
  ...
}
 

Ceci est fait en utilisant le mot clé "extend".

Existe-t-il un équivalent simple à ce mot clé en C ++?

197voto

Rapptz Points 10135

Généralement, cela se justifie pas en C++, comme d'autres réponses ici ont noté. En C++, nous avons tendance à définir les types génériques basés sur d'autres contraintes autres que "hérite de cette classe". Si tu voulais vraiment le faire, c'est assez facile à faire en C++11 et <type_traits>:

#include <type_traits>

template<typename T>
class observable_list {
    static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
    // code here..
};

Cette brise beaucoup de concepts que les gens attendent en C++. Il est préférable d'utiliser des astuces comme la définition de vos propres traits. Par exemple, peut - observable_list veut accepter n'importe quel type de conteneur qui a les typedefs const_iterator et begin et end membre de la fonction qui retourne l' const_iterator. Si vous restreindre à des classes qui héritent de list ensuite un utilisateur qui dispose de son propre type qui n'héritent pas de list mais fournit ces fonctions de membre et de typedefs serait pas en mesure d'utiliser votre observable_list.

Il y a deux solutions à ce problème, l'un d'eux est de ne pas contrainte de rien et s'appuient sur duck-typing. Un gros con de cette solution est qu'elle implique une énorme quantité d'erreurs qui peut être difficile pour les utilisateurs de grok. Une autre solution consiste à définir les traits de contrainte le type fourni pour répondre aux exigences d'interface. Le gros con de cette solution est qu'elle implique supplémentaire de l'écriture qui peut être considéré comme ennuyeux. Cependant, le côté positif, c'est que vous serez en mesure d'écrire vos propres messages d'erreur à la static_assert.

Pour être complet, la solution de l'exemple ci-dessus est donné:

#include <type_traits>

template<typename...>
struct void_ {
    using type = void;
};

template<typename... Args>
using Void = typename void_<Args...>::type;

template<typename T, typename = void>
struct has_const_iterator : std::false_type {};

template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};

struct has_begin_end_impl {
    template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
                         typename End   = decltype(std::declval<const T&>().end())>
    static std::true_type test(int);
    template<typename...>
    static std::false_type test(...);
};

template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};

template<typename T>
class observable_list {
    static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
    static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
    // code here...
};

Il y a beaucoup de concepts illustré dans l'exemple ci-dessus que la vitrine de C++11. Certains termes de recherche pour les curieux sont variadic templates, SFINAE, expression SFINAE, et le type de traits.

119voto

j_random_hacker Points 28473

Je suggère l'utilisation de Boost statique affirmer fonction de concert avec is_base_of de l'augmentation Traits de Type bibliothèque:

template<typename T>
class ObservableList {
    BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
    ...
};

Dans certains autres, les cas les plus simples, vous pouvez simplement suivre la déclaration d'un modèle global, mais seulement de définir (explicitement ou partiellement spécialisés) pour les types valides:

template<typename T> my_template;     // Declare, but don't define

// int is a valid type
template<> my_template<int> {
    ...
};

// All pointer types are valid
template<typename T> my_template<T*> {
    ...
};

// All other types are invalid, and will cause linker error messages.

[Modification mineure 6/12/2013: à l'Aide d'une déclara-mais-pas-modèle défini par l'entraînera dans l'éditeur de liens, pas de compilateur, les messages d'erreur.]

79voto

jalf Points 142628

La solution la plus simple, qui n'ont encore mentionné, est de simplement ignorer le problème. Si j'essaie d'utiliser un int comme un type de modèle dans un modèle de fonction qui attend une classe de conteneur comme vecteur ou d'une liste, puis j'ai obtiendrez une erreur de compilation. Brut et simple, mais il résout le problème. Le compilateur va essayer d'utiliser le type que vous spécifiez, et si cela échoue, il génère une erreur de compilation.

Le seul problème avec cela est que les messages d'erreur que vous obtenez sont va être difficile à lire. C'est pourtant un moyen très courant pour ce faire. La bibliothèque standard est plein de fonction ou une classe de modèles qui s'attendent à un certain comportement de type de modèle, et de ne rien faire pour vérifier que les types utilisés sont valides.

Si vous voulez plus beau des messages d'erreur (ou si vous voulez attraper cas qui ne produit pas d'erreur de compilation, mais ne comprends toujours pas le sens), vous pouvez, en fonction de la complexité que vous voulez faire, utilisez le Boost statique de l'affirmer, le coup de pouce concept_check de la bibliothèque.

Avec une mise à jour du compilateur vous avez une intégrée static_assert, qui pourrait être utilisé à la place.

12voto

Barry Carr Points 340

Pour autant que je sais que ce n'est actuellement pas possible en C++. Toutefois, il est prévu d'ajouter une fonctionnalité appelée "concepts" dans le nouveau C++0x standard qui assurent la fonctionnalité que vous recherchez. Cet article de Wikipédia sur le C++0x expliquer des concepts plus en détail, avec toutes les fonctionnalités ajoutées à la C++0x standard.

Je sais que cela ne résout pas votre problème immédiat, mais il y a certains compilateurs C++ qui ont déjà commencé à ajouter des fonctionnalités de la nouvelle norme, de sorte qu'il pourrait être possible de trouver un compilateur qui a déjà mis en œuvre les concepts de fonctionnalité.

9voto

catphive Points 1645

Résumé: Ne pas le faire.

j_random_hacker réponse vous dit comment faire. Cependant, je tiens également à souligner que vous devriez pas le faire. Le point de l'ensemble des modèles est qu'ils peuvent accepter n'importe quel type, de Java et de style contraintes de type break.

Java du type de contraintes sont d'un bug pas une fonction. Ils sont là parce que Java n'type d'effacement sur les médicaments génériques, de sorte que Java ne peuvent pas comprendre comment appeler des méthodes basées sur la valeur des paramètres de type seul.

C++ a en revanche aucune restriction. Modèle des types de paramètres peuvent être de tout type compatible avec les opérations qu'ils sont utilisés. Il n'a pas à être une classe de base commune. Ceci est similaire à Python "Duck Typing", mais fait au moment de la compilation.

Un exemple simple montrant la puissance de modèles:

// Sum a vector of some type.
// Example:
// int total = sum({1,2,3,4,5});
template <typename T>
T sum(const vector<T>& vec) {
    T total = T();
    for (const T& x : vec) {
        total += x;
    }
    return total;
}

De cette somme en fonction de la somme d'un vecteur de n'importe quel type qui prennent en charge les opérations correctes. Il fonctionne avec les deux primitives comme int/long/float/double, et définies par l'utilisateur les types numériques que la surcharge de l'opérateur+=. Heck, vous pouvez même utiliser cette fonction pour rejoindre les cordes, puisqu'elles support +=.

Pas de boxing/unboxing de primitives est nécessaire.

Notez qu'il construit également de nouvelles instances de T à l'aide de T(). C'est trivial en C++ à l'aide implicite des interfaces, mais pas vraiment possible en Java avec des contraintes de type.

Alors que les modèles C++ n'ont pas explicite les contraintes de type, ils sont toujours de type coffre-fort, et ne sera pas compilée avec le code qui ne prend pas en charge les opérations correctes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X