Forum: PC-Programmierung [C++] Cast std::vector<int*> nach std::vector<const int*>


von Da D. (dieter)


Lesenswert?

Moin,

ich habe da gerade in Cast-Problem. Angenommen, ich hab folgende 2 
Methoden:
1
class example
2
{
3
std::vector<int*> list;
4
5
std::vector<int*>& getList() {return list;}
6
const std::vector<int*>& getList() const {return list;}
7
}

Bei der zweiten Methode ist sichergestellt, dass die zurückgegebene 
Liste nicht verändert werden kann. Aber die Variablen, auf die die 
Pointer zeigen können nach wie vor verändert werden. Ich würde nun gerne 
sicherstellen, dass wenn die Instanz von example const ist, auch über 
getList() die Variablen, deren Adressen in der Liste stehen, nicht 
geändert werden können. Die Methode soll also so aussehen:
1
const std::vector<const int*>& getList() const;

Dazu brauche ich aber nun in der Implementierung einen cast. Ich habe 
folgendes probiert:
1
const std::vector<const int*>& getList() const {return static_cast<const std::vector<const int*>>(list);}

Das ergibt leider folgende Fehlermeldung (Visual Studio 2013):
1
error C2440: 'static_cast' : cannot convert from 'std::vector<int*,std::allocator<_Other>>' to 'const std::vector<const int*,std::allocator<_Ty>>'

Hat jemand einen Tipp, wie man solch einen cast hinbekommen kann?

C++11 Sprachfeatures zur Lösung sind erlaubt. C++14 leider nicht.

von Sebastian V. (sebi_s)


Lesenswert?

Instanzen von einer Klasse mit unterschiedlichen Template Parametern 
sind für den Compiler zwei völlig unterschiedliche Klassen. Dein 
vector<int*> und vector<const int*> sind für den Compiler also genauso 
verschieden wie string und vector<int>. Irgendwelche Casts zwischen 
diesen Typen sind imho nicht möglich. Höchstens mit Gewalt und 
reinterpret_cast, aber das würde ich nicht empfehlen. Eigentlich bleibt 
dann nur eine Kopie des vectors anzulegen.

: Bearbeitet durch User
von Mikro 7. (mikro77)


Lesenswert?

Da D. schrieb:
> Ich würde nun gerne
> sicherstellen, dass wenn die Instanz von example const ist, auch über
> getList() die Variablen, deren Adressen in der Liste stehen, nicht
> geändert werden können.

Darüber stolpert wohl jeder nach einer gewissen Zeit mit C++.

Sebastian V. schrieb:
> Instanzen von einer Klasse mit unterschiedlichen Template Parametern
> sind für den Compiler zwei völlig unterschiedliche Klassen.

Das mag aus Compilersicht so sein, es ist aber alles andere als 
"logisch" aus C++ (Anwender) Sicht. Daher ist es auch in der "Defective 
C++" Liste gelandet:

"For example, if your function accepts const std::vector<const char*>& 
(which is supposed to mean "a reference to an immutable vector of 
pointers to immutable built-in strings"), and I have a 
std::vector<char*> object ("a mutable vector of mutable built-in 
strings"), then I can't pass it to your function because the types 
aren't convertible. You have to admit that it doesn't make any sense, 
because your function guarantees that it won't change anything, and I 
guarantee that I don't even mind having anything changed, and still the 
C++ type system gets in the way and the only sane workaround is to copy 
the vector."

http://yosefk.com/c++fqa/defective.html#defect-7

von Chris F. (chfreund) Benutzerseite


Lesenswert?

Das ist doch totaler Unsinn, die Vektoren sind Sequenzcontainer deren 
Referenz kann man nicht so umcasten, so dass sich der Inhaltstyp ändert.

Wenn Du einen Zeiger auf eine Struktur bekommst in der der Anfang einer 
verketteten Liste ist, dann kannst Du nicht durch rumbasteln an der 
Struktuzr die Elementtypen der Liste beeinflussen.

Das ist auch keine Schwäche oder ein "Defekt" von C++. Der passende 
C-Code wäre ein struct:
1
struct listentyp
2
{
3
  listentyp * pNext;
4
  listentyp * pPrevious;
5
  anderertyp * pszData;
6
}

Wenn ich nun ein listentyp var1 bekomme dann kann ich nicht durch 
umcasten von var1 in etwas anderes auf einmal alle pszData in der 
gesamten Liste ändern.

von Mikro 7. (mikro77)


Lesenswert?

Chris F. schrieb:
> Das ist doch totaler Unsinn...

"totaler Unsinn" Tatsächlich? ;-)

Was ist mit const correctness?

Chris F. schrieb:
> der passende C-Code...

C++ ist aber nicht C. In C++ möchte und kann man gerade die 
Implementierungsdetails (effizient) verbergen.

@TS: Ein gängiger Workaround ist es selbst eine "Range" statt des 
Containers zurück zu geben. Eine fixe Range ist alles was über bleibt, 
wenn der Container und seine Elemente nicht modifzierbar sind (wäre 
schön wenn es die Range einmal in die Sprache/STL schaffen würde.) Beim 
Vector also bspw. einen Zeiger auf das erste Element (int const*) und 
die Anzahl der Elemente (size_t). Grundsätzlich (STL Container) würde 
man das wohl über Iteratoren lösen.

von Rene H. (Gast)


Lesenswert?

Mikro 7. schrieb:
> Element (int const*) und die Anzahl der Elemente (size_t). Grundsätzlich
> (STL Container) würde man das wohl über Iteratoren lösen.

Würde ich auch auf dem Weg lösen. Ein weiterer Punkt ist, dass man ein 
const einfacher weg casten kann, als hinzu casten.

Auf C casts würde ich gänzlich verzichten.

Scott Meyers hat mit Effective C++ dem Thema const ein Kapitel gewidmet. 
Das kann ich nur empfehlen.

Grüsse,
René

von Rolf M. (rmagnus)


Lesenswert?

Das Thema hat eigentlich erstmal nix mit Templates oder Containern zu 
tun. Auch mit einem Zeiger statt einem Vektor hat man das Problem. 
Hättest du also einen Zeiger auf einen int und willst nun die Adresse 
dieses Zeiger in einer Form, dass du darüber den int nicht ändern 
kannst, hast du das gleiche Problem:
1
   int* p;
2
   const int ** q = &p; // Fehler: Inkompatible Typen

von Mikro 7. (mikro77)


Lesenswert?

1
int *p ;
2
int const * const *q = &p ;

von Rene H. (Gast)


Angehängte Dateien:

Lesenswert?

Rolf M. schrieb:
> Das Thema hat eigentlich erstmal nix mit Templates oder Containern
> zu
> tun. Auch mit einem Zeiger statt einem Vektor hat man das Problem.
> Hättest du also einen Zeiger auf einen int und willst nun die Adresse
> dieses Zeiger in einer Form, dass du darüber den int nicht ändern
> kannst, hast du das gleiche Problem:   int* p;
>    const int ** q = &p; // Fehler: Inkompatible Typen

Natürlich geht das, man muss lediglich einen const cast verwenden. Und 
es hat sehr wohl mit Template zu tun. Sorry Rolf.

Beispiel:
1
#include <stdio.h>
2
3
int main (int argc, char** argv)
4
{
5
  int r =5;
6
  int *p = &r;
7
8
9
  const int ** q = const_cast<const int**>(&p);
10
  
11
  printf ("*p:%d **q:%d\n", *p, **q);
12
13
  return 0;
14
}

im Anhang das C++ File. Kompilieren mit
1
g++ -o const_test const_test.cpp

Output:
1
*p:5 **q:5

Grüsse,
René

von Chris F. (chfreund) Benutzerseite


Lesenswert?

Es bleibt dabei, man kann nicht durch Typumwandlung eines Containers die 
Inhaltstypen ändern. Das ist hier der Fehler.

Der einzige Beitrag der zu einer Lösung des Problems vom TE führt ist 
der Hinweis von Sebastian mit der Kopie. Der Konstruktor von vector kann 
mit einer Range begin->end befüllt werden.
1
  int a=1,b=2,c=3;
2
  std::vector<int*> intVec;
3
  intVec.push_back(&a);
4
  intVec.push_back(&b);
5
  intVec.push_back(&c);
6
  const std::vector<const int* const> constintVec(intVec.begin(), intVec.end()) ;
7
  *(intVec.at(1)) = 99;
8
  assert((*(intVec.at(1)))==(*(constintVec.at(1))));

von Rene H. (Gast)


Lesenswert?

Die Person, die Lösungen resp. Posts (von Chris F. und mir) mit minus 
Bewertungen bewertet, darf gerne ohne sich zu outen, eine Lösung 
formulieren. Wir sind alle sehr gespannt!

Grüsse,
René

PS: die Mühe mache ich mir hier nicht mehr, zu posten.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo Da,

Da D. schrieb:
> Hat jemand einen Tipp, wie man solch einen cast hinbekommen kann?

Konform wirst Du den cast nicht hin bekommen, da es wirklich zwei 
unterschiedliche Typen sind und (recht akademisch) die Implementierungen 
auch total unterschiedlich sein könnten. Ein static_cast/const_cast über 
void* würde sicher funktionieren, aber das muss nicht funktionieren.

Eine nicht konstante Referenz auf einen member zurück zugeben ist eh 
recht "sinnfrei", dann kannst Du den member auch gleich public machen.

Wenn es Dir aber nicht darum geht, dass ein Nutzer Elemente aus list 
entfernen oder hinzufügen kann, sondern nur Operationen auf Elementen 
aus list verwenden kann, die den Zustand der Elemente auch ändern 
können, würden sich Iteratoren als Interface anbieten:
1
std::vector< int* >::iterator begin() {
2
    return list.begin();
3
}
4
5
std::vector< int* >::iterator end() {
6
    return list.end();
7
}
8
9
std::vector< int* >::const_iterator begin() const {
10
    return list.begin();
11
}
12
13
std::vector< int* >::const_iterator end() const {
14
    return list.begin();
15
}

mfg Torsten

Edit: Sorry, habe übersehen, dass Mikro das schon im dritten Post 
vorgeschlagen hatte.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Rene H. schrieb:
> Rolf M. schrieb:
>> Das Thema hat eigentlich erstmal nix mit Templates oder Containern
>> zu
>> tun. Auch mit einem Zeiger statt einem Vektor hat man das Problem.
>> Hättest du also einen Zeiger auf einen int und willst nun die Adresse
>> dieses Zeiger in einer Form, dass du darüber den int nicht ändern
>> kannst, hast du das gleiche Problem:   int* p;
>>    const int ** q = &p; // Fehler: Inkompatible Typen
>
> Natürlich geht das, man muss lediglich einen const cast verwenden.

Das erstaunt mich doch sehr. Eigentlich kenne ich const_cast nur als 
Mittel, um const wegzucasten. Dass man es auf zweiter Pointer-Ebene auch 
benutzen könnte, um ein const dazuzucasten, darauf wäre ich im Leben 
nicht gekommen.

von Hans (Gast)


Lesenswert?

Du könntest statt int* einen Typ in dem vector speichern, der sich wie 
ein Pointer verhält, aber die gewünschte constness beibehält:

http://en.cppreference.com/w/cpp/experimental/propagate_const

Ist zwar noch nicht Standard, aber kannst Du ja auch selber 
implementieren.

von Mikro 7. (mikro77)


Lesenswert?

Rolf M. schrieb:
> Hättest du also einen Zeiger auf einen int und willst nun die Adresse
> dieses Zeiger in einer Form, dass du darüber den int nicht ändern
> kannst, hast du das gleiche Problem:
>    int* p;
>    const int ** q = &p; // Fehler: Inkompatible Typen

Die Zuweisung ist falsch! [http://c-faq.com/ansi/constmismatch.html]

Korrekt ist:

Mikro 7. schrieb:
> int const * const *q = &p ;

Rene H. schrieb:
> const int ** q = const_cast<const int**>(&p);

Rolf M. schrieb:
> Das erstaunt mich doch sehr. Eigentlich kenne ich const_cast nur als
> Mittel, um const wegzucasten.

Macht es jetzt Sinn!?

von Rolf M. (rmagnus)


Lesenswert?

Mikro 7. schrieb:
> Rolf M. schrieb:
>> Hättest du also einen Zeiger auf einen int und willst nun die Adresse
>> dieses Zeiger in einer Form, dass du darüber den int nicht ändern
>> kannst, hast du das gleiche Problem:
>>    int* p;
>>    const int ** q = &p; // Fehler: Inkompatible Typen
>
> Die Zuweisung ist falsch! [http://c-faq.com/ansi/constmismatch.html]

Äh ja, genau deshalb hab ich sie doch hingeschrieben - um zu zeigen, 
dass sie falsch ist.

> Korrekt ist:
>
> Mikro 7. schrieb:
>> int const * const *q = &p ;
>
> Rene H. schrieb:
>> const int ** q = const_cast<const int**>(&p);
>
> Rolf M. schrieb:
>> Das erstaunt mich doch sehr. Eigentlich kenne ich const_cast nur als
>> Mittel, um const wegzucasten.
>
> Macht es jetzt Sinn!?

Auch das ist genau wie ich geschrieben habe: Ich bin erstaunt, da ich 
dachte, const_cast sei nur zum wegcasten von const da und nicht zum 
hinzucasten. Auch aus dem Stroustrup geht das nicht so eindeutig 
hervor.
Dass das von "Mikro" funktioniert, erstaunt mich noch mehr. Warum macht 
das zusätzliche const beim int jetzt nichts mehr aus, wenn man auch noch 
den Zeiger const macht? Die sollten doch eigentlich nichts mit einander 
zu tun haben. Die Regel, nach der ich dachte, dass es funktioniert, gibt 
es wohl so nicht. Mir fehlt jetzt eine Regel, die stattdessen gilt.

von Mikro 7. (mikro77)


Lesenswert?

Rolf M. schrieb:
> Mikro 7. schrieb:
>> Die Zuweisung ist falsch! [http://c-faq.com/ansi/constmismatch.html]
>
> Äh ja, genau deshalb hab ich sie doch hingeschrieben - um zu zeigen,
> dass sie falsch ist.

Wenn du das "weißt", weißt du denn auch, dass sie nicht zum 
Eingangsposting paßt? Du würdest versuchen einen "std::vector<int 
const>&" zurück zu liefern statt "std::vector<int const> const&"!

Das hier ist die äquivalente Situation zum Eingangsposting:

>> Mikro 7. schrieb:
>>> int const * const *q = &p ;

> Auch das ist genau wie ich geschrieben habe: Ich bin erstaunt...

Nö. Vielleicht (nochmal?) in Ruhe die Postings hier und die FAQ lesen?!

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.