const/non-const Funktionspärchen



  • Hallo,

    ich kann mir bei const/non-const Funktionspärchen nie merken, welcher cast gültig ist. Ich möchte zwei von der Funktionalität identische Funktionen durch eine Funktion implementieren, eine const- und eine non-const-Variante. Da gibt's jetzt zwei Möglichkeiten:

    1. Implementierung in der const-Funktion
      In der non-const Funktion wird nach const gecastet, die const-Funktion aufgerufen und das Ergebnis nach non-const gecastet.

    2. Implementierung in der non-const-Funktion
      In der const Funktion wird nach non-const gecastet, die non-const-Funktion aufgerufen und das Ergebnis nach const gecastet.

    Nach meinem Verständnis sollte Variante 1 die richtige sein, ich darf ein non-const Objekt nach const casten. Weil ich an dieser Stelle weiß, dass das Objekt non-const ist, darf ich es auch wieder von const nach non-const zurückcasten.

    Variante 2 sollte falsch sein, da die non-const Funktion das Objekt verändern könnte, und das darf bei einem const Objekt nicht passieren. Also ist der cast von const nach non-const nicht erlaubt. Oder darf ich das hier brechen, weil ich weiß, dass auch die non-const Variante das Objekt nicht verändert?

    Im konkreten Beispiel funktioniert das für iteratoren nicht, ein iterator kann zwar in einen const_iterator konvertiert werden, aber nicht umgekehrt. Für die Zeigervariante funktioniert das.

    Was ist also die korrekte Lösung für iteratoren?

    #include <vector>
    #include <algorithm>
    #include <iostream>
    using namespace std;
    
    struct Test
    {
    	std::vector<int> v;
    	
    	using iterator		 = std::vector<int>::iterator;
    	using const_iterator = std::vector<int>::const_iterator;
    	
    	iterator find( int val )
    	{
    		return std::find( v.begin(), v.end(), val );
    	}
    	
    	const_iterator find( int val ) const
    	{
    		return const_cast<Test*>( this )->find( val );
    	}
    	
    	int* find2( int val )
    	{
    		return const_cast<int*>( const_cast<Test const*>( this )->find2( val ));
    	}
    	
    	int const* find2( int val ) const
    	{
    		auto pos = std::find( v.begin(), v.end(), val );
    		return pos == v.end() ? nullptr : &(*pos);
    	}
    };
    
    
    int main() 
    {
    	Test c;
    	Test const n;
    	
    	c.find( 13 );
    	n.find( 13 );
    	
    	c.find2( 13 );
    	n.find2( 13 );
    	
    	return 0;
    }
    

    Falls sich jemand fragen sollte:
    Ich habe die Variablennamen c und n vertauscht, c sollte eigentlich const sein und n non-const.



  • @DocShoe
    Eine Frage: Warum nutzt du nicht auto? Das wurde doch gerade dazu erfunden, um das Iteratoren-Handling zu vereinfachen:

    #include <vector>
    #include <algorithm>
    #include <iostream>
    using namespace std;
    
    struct Test
    {
    	std::vector<int> v;
    
    	/*using iterator = std::vector<int>::iterator;
    	using const_iterator = std::vector<int>::const_iterator;*/
    
    	auto find(int val) 
    	{
    		return std::find(v.begin(), v.end(), val);
    	}
    
    	auto find(int val) const
    	{
    		return std::find(v.begin(), v.end(), val);
    	}
    
    	auto* find2(int val)
    	{
    		auto pos = std::find(v.begin(), v.end(), val);
    		return pos == v.end() ? nullptr : &(*pos);
    	}
    
    	auto* find2(int val) const
    	{
    		auto pos = std::find(v.begin(), v.end(), val);
    		return pos == v.end() ? nullptr : &(*pos);
    	}
    };
    
    
    int main()
    {
    	Test c;
    	Test const n;
    
    	c.find(13);
    	n.find(13);
    
    	*c.find2(13) = 14; 
    	//*n.find2(13) = 14;
    
    	return 0;
    }
    

    Sorry, habe noch die Funktion find2 nachgezogen.



  • Also angenommen, die find Funktion ist jetzt nicht ganz so trivial, dann möchte ich den Code für die const/non-const Varianten nicht duplizieren, selbst wenn das nur fünf Zeilen sind.



  • Die Const Correctness ist hier eigentlich sehr cool. Weil abhängig von const bei den find Funktionen auch die entsprechenden const std::find Funktionen aufgerufen wird. Entsprechend dessen liefert automatisch auch const Iteratoren zurück.

    Wenn du Visual Studio nutzt, schaue dir mal die Definitionen im IntelliSense an.


    Edit: Hab deinen neuen Beitrag zu spät gelesen.



  • Also, ich bin leider kein Freund von const_cast, da ich schon so manche Embedded C++ Compiler gesehen habe, welche const auf ihre ganz eigene Weise interpretiert haben.

    Deswegen habe ich mal ein wenig gegrübelt. Du sagst dass die find Funktion nicht trivial ist, also warum nicht eine eigene Template-Find Funktion schreiben`?

    #include <vector>
    #include <algorithm>
    #include <iostream>
    using namespace std;
    
    
    template <class _InIt, class _Ty>
    _InIt MySpecialFind(_InIt _First, const _InIt _Last, const _Ty& _Val) 
    {
    	// Fill me
    	return std::find(_First, _Last, _Val);
    }
    
    
    struct Test
    {
    	std::vector<int> v;
    
    	
    	auto find(int val) 
    	{
    		return MySpecialFind(v.begin(), v.end(), val);
    	}
    
    	auto find(int val) const
    	{
    		return MySpecialFind(v.begin(), v.end(), val);
    	}
    };
    
    
    int main()
    {
    	Test c;
    	Test const n;
    
    	c.find(13);
    	n.find(13);
    
    	//*c.find2(13) = 14; 
    	//*n.find2(13) = 14;
    
    	return 0;
    }
    


  • @Quiche-Lorraine

    Ja, auf sowas wird's wohl irgendwie hinauslaufen. Hatte nur gehofft, dass man das analog zu find2 machen könnte, aber die beiden iterator-Typen sind halt unterschiedlich und eben nicht der gleiche Typ mit anderen Qualifizierern.
    Danke für deinen Vorschlag.


Anmelden zum Antworten