2010-01-20 6 views
23

Considérez cettevecteur et const

void f(vector<const T*>& p) 
{ 
} 
int main() 
{ 
    vector<T*> nonConstVec; 
    f(nonConstVec); 
} 

Ce qui suit ne pas compile.The chose est que vector<T*> ne peut pas être converti en vector <const T*>, et qui semble illogiquement pour moi, parce qu'il existe une conversion implicite T*-const T*. Pourquoi est-ce ?

vector<const T*> ne peut pas être converti en vector <T*> aussi, mais ce qui est normal puisque const T* ne peut pas être converti implicitement T*.

Répondre

38

J'ai ajouté quelques lignes à votre code. Cela suffit pour faire comprendre pourquoi ce n'est pas autorisée:

void f(vector<const T*>& p) 
{ 
    static const T ct; 
    p.push_back(&ct); // adds a const T* to nonConstVec ! 
} 
int main() 
{ 
    vector<T*> nonConstVec; 
    f(nonConstVec); 
    nonConstVec.back()->nonConstFunction(); 
} 
+0

super, je n'ai jamais pensé à cela –

+0

si c'était la raison, la conversion en 'const vecteur ' devrait fonctionner – marcin

+2

@marcin: T C'est théoriquement sûr, mais il n'y a aucun moyen pour le compilateur de le déterminer sans une connaissance détaillée de 'std :: vector'. De plus, pour le rendre légal, il faudrait modifier de façon complexe les règles de la séquence de conversion. Quelles sont les exigences sur le 'modèle classe Foo' pour un' Foo 'à être casté à un' Foo Foo '? Rappelez-vous, 'Foo' dans ce contexte pourrait être un modèle de pointeur intelligent ou autre chose, pas nécessairement un conteneur. – MSalters

22

vector<T> et vector<const T> sont des types non apparentés. Le fait que T peut être converti en const T ne signifie pas une chose ici.

Vous devez y penser du point de vue du système de type. Instantiated vector<int> n'a rien en commun avec vector<const int>.

+1

Grande réponse. Cela a clarifié pour moi exactement ce que c'est que c'est illégal et pourquoi, du point de vue du compilateur, ce n'est pas autorisé. Les autres réponses expliquent pourquoi c'est peut-être une mauvaise idée, ce qui est bon à savoir, mais beaucoup de code juridique est une mauvaise idée. – Praxeolitic

0

C'est ainsi que fonctionnent les modèles - aucune conversion n'est appliquée aux paramètres du modèle, de sorte que les deux vecteurs sont de types complètement différents.

+0

Que comment passer mon vecteur à la fonction qui obtient le vecteur ? Je ne veux certainement pas changer la signature de la fonction car c'est comme un commentaire qui dit qu'elle ne va pas modifier l'objet passé. – user152508

+0

Eh bien, vous devrez le changer, j'ai peur. Si vous voulez dire que vous ne changez pas le vecteur, alors vous voulez f (const vector &); –

+0

f (const &) signifie que le vecteur (contenant des pointeurs) ne sera pas lui-même modifié, mais les objets pointés vers – user152508

0

Les modèles sont un peu étranges de cette façon. Le fait qu'il y ait une conversion implicite de T en U ne signifie pas qu'il y a une conversion implicite de XXX en XXX. Cela peut arriver, mais il faut beaucoup de travail supplémentaire dans le code du gabarit pour que cela se produise, et je doute que les techniques étaient connues quand std::vector a été conçu (plus précisément, je suis sûr qu'ils n'étaient pas connus).

Éditer: Des problèmes comme celui-ci font partie de la motivation derrière l'utilisation des itérateurs. Même si un container of X n'est pas implicitement convertible en container of const X, un container<X>::iteratorest implicitement convertible en container<X>::const_iterator.

Si vous remplacez votre:

void f(vector<const T*>& p) {} 

avec:

template <class const_iter> 
void f(const_iter b, const_iter e) {} 

Puis:

int main() { 
    vector<T*> nonConstVec; 
    f(nonConstVec.begin(), nonConstVec.end()); 
    return 0; 
} 

sera très bien - et il en sera:

vector<T const *> constVec; 
f(constVec.begin(), constVec.end()); 
0

Les deux vector<const T*> et vector<T*> sont des types complètement différents. Même si vous écrivez const T* à l'intérieur de votre main(), votre code ne compilera pas. Vous devez fournir une spécialisation dans Main.

Les compiles suivantes:

#include<vector> 
using namespace std; 

template<typename T> 
void f(vector<const T*>& p) 
{ 
} 
int main() 
{ 
    vector<const int*> nonConstVec; 
    f(nonConstVec); 
} 
3

Comme d'autres l'ont dit, les conversions ne sont pas appliqués aux paramètres du modèle. Autrement dit,

vector<T> 

...et:

vector<const T> 

... sont des types complètement différents.

Si vous essayez de mettre en œuvre const-exactitude en ce qui concerne f() de ne pas modifier le contenu du vecteur, cela pourrait être plus long des lignes de ce que vous cherchez:

void f(vector<T>::const_iterator begin, vector<T>::const_iterator end) 
{ 
    for(; begin != end; ++begin) 
    { 
    // do something with *begin 
    } 
} 

int main() 
{ 
    vector<T> nonConstVec; 
    f(nonConstVec.begin(), nonConstVec.end()); 
} 
6

Think de la manière suivante:

vous avez deux classes comme ceci:

class V { T*  t;}; 
class VC { T const* t;}; 

Est-ce que vous attendez ces deux classes d'être automatiquement convertibles?
C'est essentiellement ce qu'est une classe de modèle. Chaque variation est un type complètement nouveau.

Ainsi vecteur < T * > et vecteur < T const * > sont des types complètement différents.

Ma première question est de savoir si vous voulez vraiment stocker des pointeurs?

Si oui, je suggère de regarder boost :: ptr_container. Cela maintient les pointeurs et les supprime lorsque le vecteur est détruit. Mais plus important encore, il traite les pointeurs contenus comme des standards normaux: le vecteur traite ses objets contenus. Ainsi, en faisant le const vectoriel vous ne pouvez accéder à ses membres const

void function(boost::ptr_vector<T> const& x) 
{ 
    x.push_back(new T); // Fail x is const. 
    x[4].plop();   // Will only work if plop() is a const member method. 
} 

Si vous n'avez pas besoin de stocker des pointeurs puis stocker les objets (pas les pointeurs) dans le récipient.

void function(std::vector<T> const& x) 
{ 
    x.push_back(T()); // Fail x is const. 
    x[4].plop();   // Will only work if plop() is a const member method. 
} 
13

Il peut être utile de montrer pourquoi il est une violation de const-correct pour effectuer la conversion que vous voulez:

#include <vector> 
const int a = 1; 

void addConst(std::vector<const int *> &v) { 
    v.push_back(&a); // this is OK, adding a const int* to a vector of same 
} 

int main() { 
    std::vector<int *> w; 
    int b = 2; 
    w.push_back(&b); // this is OK, adding an int* to a vector of same 
    *(w.back()) = 3; // this is OK, assigning through an int* 
    addConst(w);  // you want this to be OK, but it isn't... 
    *(w.back()) = 3; // ...because it would make this const-unsafe. 
} 

Le problème est que vector<int*>.push_back prend un pointeur vers non-const (que j'appellerai désormais un "pointeur non-const"). Cela signifie qu'il pourrait modifier la pointe de son paramètre. Spécifiquement dans le cas du vecteur, il peut renvoyer le pointeur à quelqu'un d'autre qui le modifie. Vous ne pouvez donc pas passer un pointeur const à la fonction push_back de w, et la conversion que vous voulez n'est pas sûre même si le système de template la supporte (ce qui n'est pas le cas). Le but de const-safety est de vous empêcher de passer un pointeur de const à une fonction qui prend un pointeur non-const, et c'est comme ça qu'elle fait son travail. C++ vous oblige à dire spécifiquement si vous voulez faire quelque chose de dangereux, donc la conversion ne peut certainement pas être implicite. En fait, à cause du fonctionnement des templates, ce n'est pas possible du tout (voir plus loin).

Je pense que C++ pourrait, en principe, conserver en permettant une conversion vector<T*>&-const vector<const T*>& const-sécurité, tout comme int ** à const int *const * est sûr. Mais c'est à cause de la façon dont le vecteur est défini: il ne serait pas nécessairement sécurisé pour les autres modèles. De même, il pourrait en théorie permettre une conversion explicite.En fait, elle permet une conversion explicite, mais seulement pour les objets, pas de références ;-)

std::vector<const int*> x(w.begin(), w.end()); // conversion 

La raison pour laquelle il ne peut pas le faire pour des références est parce que le système de modèle ne peut pas supporter. Un autre exemple qui serait rompu si la conversion a permis:

template<typename T> 
struct Foo { 
    void Bar(T &); 
}; 

template<> 
struct Foo<const int *> { 
    void Baz(int *); 
}; 

Maintenant, Foo<int*> ne possède pas de fonction Baz. Comment diable un pointeur ou une référence à Foo<int*> peut-il être converti en un pointeur ou une référence à Foo<const int*>?

Foo<int *> f; 
Foo<const int *> &g = f; // Not allowed, but suppose it was 
int a; 
g.Baz(&a); // Um. What happens? Calls Baz on the object f? 
4

D'autres ont déjà donné la raison pour laquelle le code que vous avez donné ne compile pas, mais j'ai une réponse différente sur la façon de traiter avec elle. Je ne crois pas qu'il existe un moyen d'apprendre au compilateur comment convertir automatiquement les deux (car cela impliquerait de changer la définition de std::vector). Le seul moyen de contourner cet ennui est de faire une conversion explicite.

La conversion en un vecteur complètement différent est insatisfaisante (gaspille de la mémoire et des cycles pour quelque chose qui devrait être complètement identique). Je suggère ce qui suit:

#include <vector> 
#include <iostream> 

using namespace std; 

typedef int T; 

T a = 1; 
T b = 2; 

void f(vector<const T*>& p) 
{ 
    for (vector<const T*>::const_iterator iter = p.begin(); iter != p.end(); ++iter) { 
     cout << **iter << endl; 
    } 
} 
vector<const T*>& constify(vector<T*>& v) 
{ 
    // Compiler doesn't know how to automatically convert 
    // std::vector<T*> to std::vector<T const*> because the way 
    // the template system works means that in theory the two may 
    // be specialised differently. This is an explicit conversion. 
    return reinterpret_cast<vector<const T*>&>(v); 
} 
int main() 
{ 
    vector<T*> nonConstVec; 
    nonConstVec.push_back(&a); 
    nonConstVec.push_back(&b); 
    f(constify(nonConstVec)); 
} 

J'utilise reinterpret_cast de déclarer que les deux choses sont les mêmes. Vous DEVRAIT se sentir sale après l'avoir utilisé, mais si vous le mettez dans une fonction par lui-même avec un commentaire pour ceux qui vous suivent, alors faites la lessive et essayez de continuer votre chemin avec une bonne conscience, bien que) avez-vous des inquiétudes à propos de quelqu'un qui arracherait le terrain sous vous?

1

en plus d'autres réponses, il vaut la peine de lire C++ FQA Lite où cela (et bien d'autres C les caractéristiques des) sont discutées à partir d'un POV critique: http://yosefk.com/c++fqa/const.html#fqa-18.1

+0

Je suis d'accord que cela vaut la peine d'être lu. Malgré son style agressif, la FQA est un bon moyen de consolider vos connaissances en C++ – AKludges