La raison pour laquelle le compilateur ne l'accepte pas est que la norme lui interdit de le faire.
La raison pour laquelle la norme ne le dit pas, c'est que le comité n'a pas introduit une règle selon laquelle const MyTemplate<Derived*>
est un type apparenté à const MyTemplate<Base*>
même si les types non-const ne sont pas liés. Et ils ne voulaient certainement pas de règle spéciale pour std :: set, car en général le langage ne fait pas de cas particuliers pour les classes de bibliothèques.
La raison pour laquelle le comité de normalisation n'a pas voulu associer ces types, c'est que MyTemplate pourrait ne pas avoir la sémantique d'un conteneur. Considérez:
template <typename T>
struct MyTemplate {
T *ptr;
};
template<>
struct MyTemplate<Derived*> {
int a;
void foo();
};
template<>
struct MyTemplate<Base*> {
std::set<double> b;
void bar();
};
Alors qu'est-ce qu'il ne signifie même passer un const MyTemplate<Derived*>
comme const MyTemplate<Base*>
? Les deux classes n'ont pas de fonctions membres communes et ne sont pas compatibles avec la mise en page. Vous auriez besoin d'un opérateur de conversion entre les deux, ou le compilateur n'aurait aucune idée de ce qu'il faut faire, qu'il soit const ou non. Mais la façon dont les modèles sont définis dans la norme, le compilateur n'a aucune idée de ce qu'il faut faire même sans les spécialisations de modèle.
std::set
lui-même pourrait fournir un opérateur de conversion, mais cela devrait juste faire une copie (*), que vous pouvez faire vous-même assez facilement. S'il y avait une telle chose comme std::immutable_set
, alors je pense qu'il serait possible d'implémenter tel qu'un std::immutable_set<Base*>
pourrait être construit à partir d'un std::immutable_set<Derived*>
juste en pointant vers le même pImpl. Même ainsi, des choses étranges se produiraient si des opérateurs non-virtuels étaient surchargés dans la classe dérivée - le conteneur de base appelait la version de base, donc la conversion pourrait désordonner l'ensemble s'il avait un comparateur non-par défaut les objets eux-mêmes au lieu de leurs adresses. Donc, la conversion viendrait avec de lourdes réserves. Mais de toute façon, il n'y a pas de immutable_set
, et const n'est pas la même chose que immuable.
En outre, supposons que Derived
soit associé à Base
par héritage virtuel ou multiple. Ensuite, vous ne pouvez pas simplement réinterpréter l'adresse d'un Derived
comme l'adresse d'un Base
: dans la plupart des implémentations, la conversion implicite change l'adresse. Il s'ensuit que vous ne pouvez pas convertir par lots une structure contenant Derived*
en tant que structure contenant Base*
sans copier la structure. Mais le standard C++ permet en fait cela pour n'importe quelle classe non-POD, pas seulement avec l'héritage multiple. Et Derived
est non-POD, car il a une classe de base. Donc, afin de soutenir ce changement à std::set
, les principes de base de l'héritage et de la structure devraient être modifiés. C'est une limitation de base du langage C++ que les conteneurs standards ne puissent pas être réinterprétés comme vous le voulez, et je ne connais pas de trucs qui pourraient les rendre sans réduire l'efficacité ou la portabilité ou les deux. C'est frustrant, mais ce genre de choses est difficile.
Depuis votre code passe un ensemble en valeur de toute façon, vous pouvez simplement faire cette copie:
std::set<Derived*> objs;
register_objects(std::set<Base*>(objs.begin(), objs.end());
[Edit: vous avez changé votre exemple de code ne pas passer par valeur. Mon code fonctionne toujours, et autant que je sache mieux que vous pouvez faire autre que de réarranger le code appelant à utiliser un std::set<Base*>
en premier lieu.]
Rédaction d'une enveloppe pour std::set<Base*>
qui assure tous les éléments sont Derived*
, la façon dont Java génériques travail , est plus facile que d'organiser la conversion que vous voulez être efficace. Ainsi, vous pouvez faire quelque chose comme:
template<typename T, typename U>
struct MySetWrapper {
// Requirement: std::less is consistent. The default probably is,
// but for all we know there are specializations which aren't.
// User beware.
std::set<T> content;
void insert(U value) { content.insert(value); }
// might need a lot more methods, and for the above to return the right
// type, depending how else objs is used.
};
MySetWrapper<Base*,Derived*> objs;
// insert lots of values
register_objects(objs.content);
(*) En fait, je pense que cela pourrait copier-on-write, qui dans le cas d'un paramètre const utilisé de la manière typique signifierait qu'il ne doit jamais faire la copie. Mais la copie sur écriture est un peu discréditée dans les implémentations STL, et même si ce n'était pas le cas, je doute que le comité veuille imposer un tel détail de mise en œuvre.
J'ai corrigé le passage de référence, c'était une faute de frappe dans ma question, dans mon code je l'ai fait correctement. –
Ils ne sont pas simplement «simplement différents»: il y a sûrement une relation entre les types, seulement ce n'est pas un simple héritage. – xtofl
mais la substitution de modèle fonctionne en substituant le paramètre de modèle dans la classe n'est-ce pas? Les 2 définitions peuvent être considérées comme std :: set_base_ptr et std :: set_derived_ptr, c'est-à-dire fondamentalement différentes. Si cela peut être fait, je serais très surpris, mais je serais heureux d'admettre que j'ai mal compris comment fonctionne la substitution de modèles (ce qui, sans doute, aiderait à étendre mes connaissances de modèle). – Goz