C++ Upcasting von Template Klasse

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Jonathan
Establishment
Beiträge: 2348
Registriert: 04.08.2004, 20:06
Kontaktdaten:

C++ Upcasting von Template Klasse

Beitrag von Jonathan »

Und gleich die nächste Frage :D

Ich habe eine Klasse, die einen Zeiter auf ein Objekt speichert und zurückgeben kann:

Code: Alles auswählen

	// for each result type there is a different base class
	template <typename t> class Base
	{
	public:
		virtual shared_ptr<t> GenerateOutput() = 0;
	};

	template<typename t> class Input : public Base<t>
	{
	public:
		Input(t value):
			m_value(make_shared<t>(value))
		{}
		Input(shared_ptr<t> value):
			m_value(value)
		{}
		virtual shared_ptr<t> GenerateOutput()
		{
			return m_value;
		}
	private:
		shared_ptr<t> m_value;
	};
Die Idee ist, dass auch andere Node-Typen von Base erben können, die dann ggf. Werte berechnen. Input ist eine Klasse die einfach nur eine Konstante zurückgibt.

Nun habe ich sowas:

Code: Alles auswählen

shared_ptr<Input<Function1D::Base>> fun = make_shared<Input<Function1D::Constant>>(0.2f);
Ich habe also eine abstrakte Klasse für Funktionen und eine Spezialisierung für bestimmte Arten von Funktionen (hier eine die einfach konstant ist).

Nun funktioniert diese Zeile allerdings nicht, da die Typen nicht konvertiert werden können. Hätte ich hier nicht meine Input Klasse sondern einfach shared_ptr würde die Zuweisung funktionierenö das ist ja Up-Casting da Function1D::Constant von Function1D::Base erbt (Function1D::Base hat eine virtuelle get_value() Funktion, weshalb das ganze sinnvoll ist).
Jetzt frage ich mich, wie ich in meine Input Klasse Up-Casting Unterstützung einbauen kann. Am besten das es implizit funktioniert und die Zeile oben so gültig wird. Muss ja eigentlich ein geläufiges Problem sein, aber wie sieht das passende Pattern für die Lösung aus?
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: C++ Upcasting von Template Klasse

Beitrag von Krishty »

seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Jonathan
Establishment
Beiträge: 2348
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C++ Upcasting von Template Klasse

Beitrag von Jonathan »

Hab ich mir angeschaut, aber so ganz komme ich damit noch nicht weiter:

Das hier geht schonmal (sanity check):
shared_ptr<Function1D::Base> funfun = make_shared<Function1D::Constant>(0.2f);


So, jetzt habe ich meiner Input Klasse ein paar Funktionen spendiert:
Input(t value):
m_value(make_shared<t>(value))
{}
Input(shared_ptr<t> value):
m_value(value)
{}

template<typename u> Input(shared_ptr<u> value):
m_value(value)
{}
template<typename u> Input& operator= (shared_ptr<u> value)
{
m_value = value;
return *this;
}
template<typename u> operator Input<u>() const
{
return Input<u>(m_value);
}


Damit kann ich jetzt das hier mache:
Node::Input<Function1D::Base> n = Node::Input<Function1D::Constant>(0.2f);

Aber wenn ich jetzt das hier probiere:
auto v = make_shared<Node::Input<Function1D::Constant>>(0.2f);
shared_ptr<Node::Input<Function1D::Base>> f1 = v;
shared_ptr<Node::Input<Function1D::Base>> f2 = static_cast<shared_ptr<Node::Input<Function1D::Base>>>(v);
shared_ptr<Node::Input<Function1D::Base>> f3 = std::static_pointer_cast<Node::Input<Function1D::Base>>(v);


Kriege ich für die erste und zweite Zeile ein:
cannot convert from 'std::shared_ptr<Node::Input<Function1D::Constant>>' to 'std::shared_ptr<Node::Input<Function1D::Base>>'
Und für die dritte:
'static_cast': cannot convert from 'Node::Input<Function1D::Constant> *' to 'Node::Input<Function1D::Base> *'

Ich meine, ein wenig macht das schon Sinn, ich kann halt jetzt die Werte von Input ineinander casten (dafür hab ich ja eine Funktion geschrieben) aber keine Zeiger auf Inputs casten. Wie erkläre ich jetzt meinem Compiler wie das geht?
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Lord Delvin
Establishment
Beiträge: 574
Registriert: 05.07.2003, 11:17

Re: C++ Upcasting von Template Klasse

Beitrag von Lord Delvin »

Sind shared pointer in C++ überhaupt kovariant? Als R/W-Box würde ich jetzt erwarten, dass sie Invariant sind und das was du da machen willst eigentlich vom Typsystem verboten werden sollte.
Ich würde jetzt mal erwarten, dass die in C++ nichtmal dieselbe Repräsentation haben müssen.
XML/JSON/EMF in schnell: OGSS
Keine Lust mehr auf C++? Versuche Tyr: Get & Get started
Benutzeravatar
Jonathan
Establishment
Beiträge: 2348
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C++ Upcasting von Template Klasse

Beitrag von Jonathan »

Uhhm, tja, keine Ahnung. Ich weiß nur, dass das was ich hier vorhabe, auf den zweiten Blick sinnvoll ist, und daher möglich sein sollte. Meine Frage ist nur, wie man das am elegantesten Umsetzen kann.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
NytroX
Establishment
Beiträge: 358
Registriert: 03.10.2003, 12:47

Re: C++ Upcasting von Template Klasse

Beitrag von NytroX »

Bin mir nicht sicher, ob ich was übersehen habe, aber ich denke mal folgendes:

template<typename t> class Input : public Base<t>

Das bedeutet:

Code: Alles auswählen

Input<T> erbt von Base<T>
Input<Constant> erbt von Base<Constant>
Input<Base> erbt von Base<Base> //huh?
Jetzt versuchst du:

Code: Alles auswählen

auto f3 = std::static_pointer_cast<Node::Input<Function1D::Base>>(v);
Oder einfacher:

Code: Alles auswählen

Input<Base> = Input<Constant>
Aber da ist ja keine Beziehung zwischen denen:
Input<Function1D::Constant> erbt NICHT von Input<Function1D::Base> (sondern nur von Base<Function1D::Base>)

Warum sollte der Cast also gehen?
Irgendwie sieht deine Vererbungsstruktur komisch aus...
Sollte das nicht so etwa aussehen?

Code: Alles auswählen

auto f3 = std::static_pointer_cast<Node::Base<Function1D::Base>>(v);

Vielleicht kannst du nochmal beleuchten, was genau du erreichen willst.
Die Base-Klasse mit der GenerateOutput() Funktion sieht erstmal sinnvoll aus. Das ist ein Interface und das willst du später implementieren/spezialisieren.
Danach bin ich vom Verständnis her leider ausgestiegen.

In welchem Teil/Namespace steht denn der Code mit "class Base", den du gepostet hast?
Ist das Function1D::Base? Oder Node::Base?
Was soll der Input<> Krams werden?
Benutzeravatar
Lord Delvin
Establishment
Beiträge: 574
Registriert: 05.07.2003, 11:17

Re: C++ Upcasting von Template Klasse

Beitrag von Lord Delvin »

NytroX hat geschrieben: 31.07.2022, 12:14 Warum sollte der Cast also gehen?
Irgendwie sieht deine Vererbungsstruktur komisch aus...
https://de.wikipedia.org/wiki/Kovarianz ... travarianz ;)

Die natürliche Varianz von Speicher ist ko für lesbaren und in für les- und schreibbaren.
XML/JSON/EMF in schnell: OGSS
Keine Lust mehr auf C++? Versuche Tyr: Get & Get started
NytroX
Establishment
Beiträge: 358
Registriert: 03.10.2003, 12:47

Re: C++ Upcasting von Template Klasse

Beitrag von NytroX »

Ah, ich hatte auch irgendwie überlesen, dass Function1D::Constant auch von Function1D::Base erbt.
Und dann erbt auch noch Input<Function1D::Constant> von Base<Function1D::Constant> und Input<Function1D::Base> von Base<Function1D::Base>
Das hat mich dann verwirrt...

Btw guter Link, das Kapitel "Kovarianz auf Arrays" erklärt das Ganze.
Du hast hier zwar ein Input<T> und kein Array<T>, aber das ist dem Typesytem herzlich egal :-P
(und die Lösung steht da sogar auch, wenn auch nicht so verständlich)

Du musst halt irgendwie an den "Inhalt" rankommen, aber wenn ich das richtig sehe, macht das ja genau deine GenerateOutput() Methode.
Also wenn deine Input<T> Klasse eine Möglichkeit hat, an T ranzukommen.
Mir ist halt leider noch nicht klar, warum du da überhaupt casten musst.

Siehe hier:
https://godbolt.org/z/P4jG37j91

Code: Alles auswählen

#include <memory>

using namespace std;

struct FB {};
struct FC : FB {
    float x = 0;
    FC(float val):x(val){}
};


template <typename T> class Base {
public:
    virtual shared_ptr<T> GenerateOutput() = 0;
};

template<typename T> class Input : public Base<T>
{
private:
    shared_ptr<T> m_value;
public:
    Input(T value):m_value(make_shared<T>(value)){}
    Input(shared_ptr<T> value):m_value(value){}
    virtual shared_ptr<T> GenerateOutput() { return m_value; }
};

int main() {
    shared_ptr<Input<FC>> f1 = make_shared<Input<FC>>(0.2f);
    auto x = f1->GenerateOutput()->x;
    auto f2 = static_cast<shared_ptr<Input<FC>>>(f1);
    shared_ptr<FC> sfc = f1->GenerateOutput();
    shared_ptr<FB> sfb = f1->GenerateOutput();
    Input<FB> f3 = Input(sfb);
    shared_ptr<Input<FB>> sifb = make_shared<Input<FB>>(f3);
    return 0;
}
Benutzeravatar
Jonathan
Establishment
Beiträge: 2348
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C++ Upcasting von Template Klasse

Beitrag von Jonathan »

Ok, ich schau mir das mal näher an, bin gerade noch unterwegs, aber vielen Dank schonmal.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Jonathan
Establishment
Beiträge: 2348
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C++ Upcasting von Template Klasse

Beitrag von Jonathan »

Ich bin jetzt an anderer Stelle im selben Projekt auf glaube ich das selbe Problem gestoßen und habe es, uhm, pragmatisch gelöst.

Nochmal kurz das Szenario: Ich habe ein Node-System (vergleichbar mit den Nodes in Blender), jeder Node verarbeitet Inputs zu einem Output. Jetzt brauch ich manchmal n Eingabewerte, das kann entweder ein Array mit n verschiedenen Werten sein, oder ein einzelner Wert, der n mal benutzt wird (man kennt das als Broadcasting in numpy / Python).

Ich habe also

Code: Alles auswählen

class Broadcast_Value
{
 t GetValue()=0;
}

class Vector : public Broadcast_Value;
class Scalar : public Broadcast Value;
Jetzt habe ich Nodes, deren Output ein Broadcast_Value sein kann, der dann wieder die Eingabe für einen anderen Node sein kann. Ein Node erwartet also z.B. einen Zeiger auf die Node-Basis Klasse die als Ausgabewert einen Broadcast_Value eines spezifischen Types hat:

Code: Alles auswählen

shared_ptr<Base<Broadcast_Value::Base<mat3>>> value
Konkret muss ich jetzt aber natürlich z.B. einen

Code: Alles auswählen

shared_ptr<Base<Broadcast_Value::Vector<mat3>>> value
übergeben (d.h. In der Mitte steht irgendwo "Vector" statt "Base"). Abstrakt gesehen, sollte das implizit konvertierbar sein, ich übergebe ja einen spezielleren Typen an etwas, das einen abstrakteren Typen erwartet. Aber C++ erlaubt die automatische Konvertierung hier nicht.

Meine bisherige Lösung ist es, der Input-Node Klasse eine cast-Funktion zu geben:

Code: Alles auswählen

template<typename t> class Input : public Base<t>
{
public:
	...
	template<typename u> shared_ptr<Input<u>> cast_ptr()
	{
		return make_shared<Input<u>>(static_cast<shared_ptr<u>>(m_value));
	}

	virtual shared_ptr<t> GenerateOutput()
	{
		return m_value;
	}
private:
	shared_ptr<t> m_value;
};
Wenn ich jetzt einen Eingabewert an einen Node übergeben will, schreibe ich jetzt:

Code: Alles auswählen

Node::make_input(Broadcast_Value::Single<float>(0.4f))->cast_ptr<Broadcast_Value::Base<float>>()
Was ein bisschen die Hölle ist, denn eigentlich würde ich lieber 0.4f oder {1, 2, 3.4, 15} schreiben können. Andererseits ist langfristig ohnehin der Plan, dass die Nodes nicht durch Code definiert werden, also wird man das hoffentlich nicht so oft brauchen.

Die Frage wäre natürlich auch, ob man den impliziten Cast doch irgendwie hinbekommen kann, oder ob es nicht viel Sinnvoller ist, eine Funktion SingleInput<float>(0.4f) zu definieren, die dann eben obige Zeile aufruft...
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Lord Delvin
Establishment
Beiträge: 574
Registriert: 05.07.2003, 11:17

Re: C++ Upcasting von Template Klasse

Beitrag von Lord Delvin »

Jonathan hat geschrieben: 05.09.2022, 11:21 Konkret muss ich jetzt aber natürlich z.B. einen

Code: Alles auswählen

shared_ptr<Base<Broadcast_Value::Vector<mat3>>> value
übergeben (d.h. In der Mitte steht irgendwo "Vector" statt "Base"). Abstrakt gesehen, sollte das implizit konvertierbar sein, ich übergebe ja einen spezielleren Typen an etwas, das einen abstrakteren Typen erwartet. Aber C++ erlaubt die automatische Konvertierung hier nicht.
Wie schonmal gesagt: Es wäre nicht korrekt, da Templates per se erstmal invariant sind und Zeiger invariant sein müssen. Zudem müsste streng genommen die Repräsentation aus einer shared_ptr-Basisklasse kommen, was vermutlich nicht so ist und in der Praxis egal sein wird. In deinem Fall ohnehin.

Man trollt sich selbst bei varianten Zeigern schnell, weil das, was eigentlich variant ist, das Lesen ist. D.h. für T <: S kannst du T* an S* zuweisen, weil du T* liest, dann den Wert nach S* konvertierst und dann in S* speicherst. T** kannst du nicht an S** zuweisen. Zumindest nicht wenn das in C++ richtig gemacht wurde. In Ada wurde das auf jeden Fall richtig gemacht; da mich das da auch mal verwirrt hat ist es in C++ im einfachen *-Fall vielleicht falsch und nur bei Templates richtig.
Jonathan hat geschrieben: 05.09.2022, 11:21 Die Frage wäre natürlich auch, ob man den impliziten Cast doch irgendwie hinbekommen kann, oder ob es nicht viel Sinnvoller ist, eine Funktion SingleInput<float>(0.4f) zu definieren, die dann eben obige Zeile aufruft...
Wollte ich auch gerade fragen. Wenn dich das so sehr beschäftigt würde ich mal schauen, ob sich da was mit Concepts/enable_if/CanBuildFrom in Verbindung mit einem CastOperator machen lässt. CastOperator ist in C++ noch fieser als sonst wo und du musst höllisch aufpassen, dass die Overloadresolution dein API noch verarbeiten kann. Auf den ersten Blick scheint C++ wenigstens Templates mit CastOperator kombinieren zu können. Ist bestimmt eine gute Übung, um was über Softwareentwicklung zu lernen :)
XML/JSON/EMF in schnell: OGSS
Keine Lust mehr auf C++? Versuche Tyr: Get & Get started
Antworten