En Java, vous pouvez définir une classe générique qui n'accepte que 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 ++?
Réponses:
Je suggère d'utiliser la fonction d'assertion statique de Boost de concert avec
is_base_of
la bibliothèque Boost Type Traits:Dans d'autres cas plus simples, vous pouvez simplement déclarer en avant un modèle global, mais ne le définir (explicitement ou partiellement) que pour les types valides:
[Modification mineure 12/06/2013: L'utilisation d'un modèle déclaré mais non défini entraînera des messages d'erreur dans l' éditeur de liens , et non dans le compilateur.]
la source
myBaseType
exactement. Avant de rejeter Boost, vous devez savoir que la plupart d'entre eux sont du code de modèle d'en-tête uniquement - il n'y a donc pas de coût de mémoire ou de temps au moment de l'exécution pour les choses que vous n'utilisez pas. De plus, les choses particulières que vous utiliseriez ici (BOOST_STATIC_ASSERT()
etis_base_of<>
) peuvent être implémentées en utilisant uniquement des déclarations (c'est-à-dire aucune définition réelle des fonctions ou des variables) afin qu'elles ne prennent pas non plus d'espace ou de temps.static_assert(std::is_base_of<List, T>::value, "T must extend list")
.my_template<int> x;
oumy_template<float**> y;
et vérifier que le compilateur les autorise, puis déclarer une variablemy_template<char> z;
et vérifier que ce n'est pas le cas.Cela n'est généralement pas justifié en C ++, comme l'ont noté d'autres réponses ici. En C ++, nous avons tendance à définir des types génériques basés sur d'autres contraintes autres que «hérite de cette classe». Si vous vouliez vraiment faire cela, c'est assez facile à faire en C ++ 11 et
<type_traits>
:Cela casse beaucoup de concepts auxquels les gens s'attendent en C ++. Il est préférable d'utiliser des astuces comme définir vos propres traits. Par exemple,
observable_list
souhaite peut-être accepter tout type de conteneur qui a les typedefsconst_iterator
et une fonction membrebegin
etend
qui retourneconst_iterator
. Si vous limitez cela aux classes qui héritent de,list
un utilisateur qui a son propre type qui n'hérite pas delist
mais fournit ces fonctions membres et typedefs ne pourra pas utiliser votreobservable_list
.Il existe deux solutions à ce problème, l'une d'elles est de ne rien contraindre et de s'appuyer sur le typage canard. Un gros inconvénient de cette solution est qu'elle implique une quantité massive d'erreurs qui peuvent être difficiles à gérer pour les utilisateurs. Une autre solution consiste à définir des traits pour contraindre le type fourni pour répondre aux exigences de l'interface. Le gros inconvénient de cette solution est qu'elle implique une écriture supplémentaire qui peut être considérée comme ennuyeuse. Cependant, le côté positif est que vous serez en mesure d'écrire vos propres messages d'erreur a la
static_assert
.Par souci d'exhaustivité, la solution à l'exemple ci-dessus est donnée:
Il existe de nombreux concepts illustrés dans l'exemple ci-dessus qui présentent les fonctionnalités de C ++ 11. Certains termes de recherche pour les curieux sont les modèles variadiques, SFINAE, l'expression SFINAE et les traits de type.
la source
template<class T:list>
un concept aussi offensant. Merci pour le conseil.La solution simple, que personne n'a encore mentionnée, consiste simplement à ignorer le problème. Si j'essaie d'utiliser un
int
comme type de modèle dans un modèle de fonction qui attend une classe de conteneur telle que vecteur ou liste, j'obtiendrai une erreur de compilation. Cru et simple, mais cela résout le problème. Le compilateur essaiera 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 seront difficiles à lire. C'est néanmoins une manière très courante de le faire. La bibliothèque standard est pleine de modèles de fonctions ou de classes qui attendent un certain comportement du type de modèle et ne font rien pour vérifier que les types utilisés sont valides.
Si vous voulez des messages d'erreur plus agréables (ou si vous voulez attraper des cas qui ne produiraient pas d'erreur de compilation, mais qui n'ont toujours pas de sens), vous pouvez, en fonction de la complexité que vous souhaitez faire, utiliser l'assertion statique de Boost ou la bibliothèque Boost concept_check.
Avec un compilateur à jour, vous avez un built_in
static_assert
, qui pourrait être utilisé à la place.la source
T
-il et d'où s'appelle ce code? Sans un certain contexte, je n'ai aucune chance de comprendre cet extrait de code. Mais ce que j'ai dit est vrai. Si vous essayez d'appelertoString()
un type qui n'a pas detoString
fonction membre, vous obtiendrez une erreur de compilation.Nous pouvons utiliser
std::is_base_of
etstd::enable_if
:(
static_assert
peut être supprimé, les classes ci-dessus peuvent être implémentées sur mesure ou utilisées à partir de boost si nous ne pouvons pas référencertype_traits
)la source
Autant que je sache, ce n'est actuellement pas possible en C ++. Cependant, il est prévu d'ajouter une fonctionnalité appelée «concepts» dans la nouvelle norme C ++ 0x qui fournit la fonctionnalité que vous recherchez. Cet article Wikipédia sur les concepts C ++ l'expliquera plus en détail.
Je sais que cela ne résout pas votre problème immédiat, mais il existe des compilateurs C ++ qui ont déjà commencé à ajouter des fonctionnalités à partir de la nouvelle norme, il est donc possible de trouver un compilateur qui a déjà implémenté la fonctionnalité de concepts.
la source
static_assert
et SFINAE, comme le montrent les autres réponses. Le problème restant pour quelqu'un venant de Java ou C #, ou Haskell (...) est que le compilateur C ++ 20 ne vérifie pas la définition par rapport aux concepts requis, ce que font Java et C #.Je pense que toutes les réponses précédentes ont perdu de vue la forêt pour les arbres.
Les génériques Java ne sont pas les mêmes que les modèles ; ils utilisent l' effacement de type , qui est une technique dynamique , plutôt que le polymorphisme au moment de la compilation , qui est une technique statique . Il devrait être évident pourquoi ces deux tactiques très différentes ne gèlent pas bien.
Plutôt que d'essayer d'utiliser une construction au moment de la compilation pour simuler une construction au moment de l'exécution, regardons ce qui
extends
fait réellement: selon Stack Overflow et Wikipedia , extend est utilisé pour indiquer le sous-classement.C ++ prend également en charge le sous-classement.
Vous affichez également une classe de conteneur, qui utilise l'effacement de type sous la forme d'un générique, et s'étend pour effectuer une vérification de type. En C ++, vous devez effectuer vous-même la machine d'effacement de type, ce qui est simple: faites un pointeur vers la superclasse.
Emballons-le dans un typedef, pour le rendre plus facile à utiliser, plutôt que de créer une classe entière, et voilà:
typedef std::list<superclass*> subclasses_of_superclass_only_list;
Par exemple:
Maintenant, il semble que List soit une interface, représentant une sorte de collection. Une interface en C ++ serait simplement une classe abstraite, c'est-à-dire une classe qui n'implémente rien d'autre que des méthodes virtuelles pures. En utilisant cette méthode, vous pouvez facilement implémenter votre exemple java en C ++, sans aucun concept ni spécialisation de modèle. Il fonctionnerait également aussi lentement que les génériques de style Java en raison des recherches de table virtuelle, mais cela peut souvent être une perte acceptable.
la source
Un équivalent qui n'accepte que les types T dérivés du type List ressemble à
la source
Résumé: ne faites pas ça.
La réponse de j_random_hacker vous indique comment faire cela. Cependant, je voudrais également souligner que vous ne devriez pas faire cela. L'intérêt des modèles est qu'ils peuvent accepter n'importe quel type compatible, et les contraintes de type de style Java rompent cela.
Les contraintes de type de Java sont un bogue pas une fonctionnalité. Ils sont là parce que Java efface le type sur les génériques, donc Java ne peut pas comprendre comment appeler des méthodes en se basant uniquement sur la valeur des paramètres de type.
C ++ d'autre part n'a pas une telle restriction. Les types de paramètres de modèle peuvent être de tout type compatible avec les opérations avec lesquelles ils sont utilisés. Il n'est pas nécessaire qu'il y ait une classe de base commune. Ceci est similaire au "Duck Typing" de Python, mais fait au moment de la compilation.
Un exemple simple montrant la puissance des modèles:
Cette fonction de somme peut additionner un vecteur de tout type prenant en charge les opérations correctes. Il fonctionne avec les deux primitives telles que int / long / float / double et les types numériques définis par l'utilisateur qui surchargent l'opérateur + =. Heck, vous pouvez même utiliser cette fonction pour joindre des chaînes, car elles prennent en charge + =.
Aucune boxe / déballage des primitives n'est nécessaire.
Notez qu'il construit également de nouvelles instances de T en utilisant T (). C'est trivial en C ++ en utilisant des interfaces implicites, mais pas vraiment possible en Java avec des contraintes de type.
Bien que les modèles C ++ n'aient pas de contraintes de type explicites, ils sont toujours sûrs de type et ne seront pas compilés avec du code qui ne prend pas en charge les opérations correctes.
la source
Ce n'est pas possible en C ++ simple, mais vous pouvez vérifier les paramètres du modèle au moment de la compilation via la vérification de concept, par exemple en utilisant le BCCL de Boost .
Depuis C ++ 20, les concepts deviennent une caractéristique officielle du langage.
la source
Assurez-vous que les classes dérivées héritent de la structure FooSecurity et que le compilateur sera bouleversé aux bons endroits.
la source
Type::FooSecurity
est utilisé dans la classe de modèle. Si la classe, passée en argument modèle, ne l'a pas faitFooSecurity
, tenter de l'utiliser provoque une erreur. Il est certain que si la classe passée dans l'argument template n'a pas FooSecurity, elle n'est pas dérivéeBase
.Utilisation du concept C ++ 20
https://en.cppreference.com/w/cpp/language/constraints cppreference donne le cas d'utilisation de l'héritage comme exemple de concept explicite:
Pour plusieurs bases, je suppose que la syntaxe sera:
GCC 10 semble l'avoir implémenté: https://gcc.gnu.org/gcc-10/changes.html et vous pouvez l'obtenir en tant que PPA sur Ubuntu 20.04 . https://godbolt.org/ Mon GCC 10.1 local ne l'a pas
concept
encore reconnu , donc je ne sais pas ce qui se passe.la source
Non.
Selon ce que vous essayez d'accomplir, il pourrait y avoir des substituts adéquats (voire meilleurs).
J'ai parcouru du code STL (sous Linux, je pense que c'est celui dérivant de l'implémentation de SGI). Il contient des «affirmations de concept»; par exemple, si vous avez besoin d'un type qui comprend
*x
et++x
, l'assertion de concept contiendra ce code dans une fonction ne rien faire (ou quelque chose de similaire). Cela nécessite une surcharge, il peut donc être judicieux de le mettre dans une macro dont la définition dépend de#ifdef debug
.Si la relation de sous-classe est vraiment ce que vous voulez savoir, vous pouvez l'affirmer dans le constructeur
T instanceof list
(sauf qu'elle est «orthographiée» différemment en C ++). De cette façon, vous pouvez tester votre façon de sortir du compilateur sans pouvoir le vérifier pour vous.la source
Il n'y a pas de mot clé pour de telles vérifications de type, mais vous pouvez insérer du code qui échouera au moins de manière ordonnée:
(1) Si vous voulez qu'un modèle de fonction n'accepte que les paramètres d'une certaine classe de base X, affectez-le à une référence X dans votre fonction. (2) Si vous souhaitez accepter des fonctions mais pas des primitives ou vice versa, ou si vous souhaitez filtrer les classes d'une autre manière, appelez une fonction d'assistance de modèle (vide) dans votre fonction qui n'est définie que pour les classes que vous souhaitez accepter.
Vous pouvez également utiliser (1) et (2) dans les fonctions membres d'une classe pour forcer ces vérifications de type sur toute la classe.
Vous pouvez probablement le mettre dans une macro intelligente pour soulager votre douleur. :)
la source
Eh bien, vous pouvez créer votre modèle en lisant quelque chose comme ceci:
Cela rendra cependant la restriction implicite, et vous ne pouvez pas simplement fournir quelque chose qui ressemble à une liste. Il existe d'autres moyens de restreindre les types de conteneurs utilisés, par exemple en utilisant des types d'itérateurs spécifiques qui n'existent pas dans tous les conteneurs, mais encore une fois, c'est plus une restriction implicite qu'une restriction explicite.
À ma connaissance, une construction qui refléterait l'instruction Java dans toute son étendue n'existe pas dans la norme actuelle.
Il existe des moyens de restreindre les types que vous pouvez utiliser dans un modèle que vous écrivez en utilisant des typedefs spécifiques à l'intérieur de votre modèle. Cela garantira que la compilation de la spécialisation de modèle pour un type qui n'inclut pas ce typedef particulier échouera, de sorte que vous pouvez sélectivement prendre en charge / ne pas prendre en charge certains types.
En C ++ 11, l'introduction de concepts devrait rendre cela plus facile, mais je ne pense pas que cela fera exactement ce que vous voudriez non plus.
la source