[C++] Template-Argument als Bedingung

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Template-Argument als Bedingung

Beitrag von Goderion »

Vielen Dank für die Antworten!
Chromanoid hat geschrieben:Zum Bitset: Ist die STD-Implementierung da so doof? http://en.cppreference.com/w/cpp/utility/bitset/bitset
Ich weiß nicht, wie Du das meinst. Wenn ich 64 Bit brauche, muss die Variable/Speicher ja mindestens 64 Bit abbilden können, also auch mindestens 64 Bit groß sein. Sollte man die Bits auf mehre Variablen verteilen, ist eine weitere Logik notwendig, um die richtige Variable zu ermitteln. Ich werde mir das std::bitset mal genauer angucken, wie das intern arbeitet. Danke für den Link.
Chromanoid hat geschrieben:Zu Map-Zugriff: Ich bin ja kein C++-Entwickler, aber die langen Zugriffszeiten bei der Map wundern mich ein bisschen. Gibt es einen Unterschied zwischen Zugriff über map.find und map[key]? Eigentlich sollte das ja eine Hashcode-Berechnung sein, dann ein Speicherzugriff und ggf. ein, zwei Vergleiche mit weiteren Speicherzugriffen.
Ich denke solche Techniken/Algorithmen lohnen sich wie gesagt erst bei einer bestimmten Anzahl von Elementen. Ich habe nochmal einen Test mit 20 Elementen gemacht, und selbst da ist die std::unordererd_map etwas langsamer, als eine lineare Suche in einem Array oder Liste.
Hier meine Messergebnisse mit 20 Elementen:
Array -> 2031 ms
Liste -> 2594 ms
dynamic_cast ->102890 ms
std::unordered_map -> 2907 ms
Win32Project4_V2.cpp
(7.69 KiB) 252-mal heruntergeladen
Chromanoid hat geschrieben:Zur Matrix: ich meinte eine ziemlich billige Form der Implementierung, die mir so in den Sinn gekommen ist.
1. Ermittle die maximale Anzahl aller Komponenten.
2. Baue einen Array mit ANZAHL_ENTITIES*MAX_ANZAHL_KOMPONENTEN aus 24bit Ganzzahlen -> "Matrix".
3. Pro Komponente halte einen Array aus Stucts mit den komponmentenspezifischen Daten zu allen Entities, die diese Komponente nutzen. In der Struct befindet sich mindestens die Zeilennummer des Entity in der "Matrix". Pro Komponente führe weitere Datenstrukturen, um die Daten geschickt zu verwalten (Quadtree usw.). Komponenten recyclen ihre Entity-spezifischen Datenstrukturen.
4. Wenn ein neues Entitiy angelegt wird, reserviere eine Zeile in der "Matrix" und nutze den Index der Zeile zur Identifikation. Registriere das Entity bei allen gewünschten Komponenten und merke dir die Indizes zu jeder Komponente in der "Matrix" - der Rest der Zeile ist 2^24 oder was auch immer.

Zahlreiche Zugriffsmuster sind damit ziemlich optimal zu erledigen. Man kann in der Matrix und bei den Komponenten Chunking betreiben, um nicht von Anfang an Platz für die maximale Anzahl Entities machen zu müssen.
Ok, ich glaube ich habe es verstanden. Wenn ich jetzt auf einer Karte 240.000 Objekte habe und es gibt 33 verschiedene Komponenten, dann habe ich ein zweidimensionales Array mit der Größe [240.000][33], oder anders ausgedrückt, eine 240.000 * 33 Matrix. Ein Objekt kennt seine Zeile in dieser Matrix und in jeder Spalte steht der Index der Komponente. Also Spalte 0 ist z.B. für die Armor-Komponente, Spalte 1 für die Weapon-Komponente, usw.. Wenn ich jetzt bei einem Objekt wissen möchte, ob und welche Armor-Komponente es hat, gucke ich in der Zeile, die dem Objekt zugewiesen ist, in die betreffende Spalte, bei Armor also Spalte 0. Also KomponenteIndex = Matrix[Objektzeile][KomponentenSpalte]; Da steht dann z.B. -1, für keine Armor, oder halt der Index/Position für die Armor im ArmorArray.

Welche Vorteile sehe ich da?
1. Eine Komponente muss nicht mehr gesucht werden, entweder sie existiert oder nicht.
2. Ein Downcast ist nicht mehr Notwendig.
3. Ich kann eine Komponte, bzw. das Array, durchgehen, ohne dass ich auf andere Daten achten muss. Also z.B. alle Licht-Komponenten.

Punkt 1 und 2 kann ich auch erreichen, wenn ich die Komponentenliste komplett ändere, bzw. gegen eine Struktur mit Pointern zu allen Komponentenarten ersetze. Oder die Zeile pro Matrix direkt im Objekt gespeichert wird. Durch solche Lösungen wird natürlich Punkt 3 nichtig.

Punkt 3 klingt zwar interessant, aber ich habe bisher keine Situation, wo ich das sinnvoll nutzen könnte. Alle Mechaniken/Systeme, die aufgrund von einer Komponente etwas machen, haben so oder so ihre eigenen zusätzlichen Strukturen.
dot hat geschrieben:Ich würde rein prinzipiell hier das Design hinterfragen; bei Anblick von dynamic_cast/typeid() sollten generell sofort immer erstmal die Alarmglocken läuten. Downcasts, egal in welcher Form, sind in der Regel Symptom eines grundlegenden Problems im Design. Ob du nun dynamic_cast, typeid oder irgendein selbst gebasteltes typeid (denn der Enum ist nix anderes) verwendest, spielt keine Rolle. Das grundlegende Problem ist, dass du Code hast, der nur ein abstraktes Interface kennt, der aber auf einmal wissen muss welche konkrete Implementierung hinter dem Interface steckt. So eine Situation zeigt in der Regel dass das Interface nicht zum Problem passt oder das Problem nicht zum Interface. Implementierungsabhängiges Verhalten sollte hinter einem Interface stecken und nicht davor, wofür bräuchte man sonst ein Interface!?
Die gute alte Downcast-Diskussion... ;-)
Die Frage ist, wie löse ich das anders? Die einfachste Lösung wäre, dass jedes Objekt einen Pointer oder Index zu jeder Komponente hat (geht in die Richtung der Matrix-Idee von Chromanoid). Ich rechne aber damit, das ich am Ende über 60 verschiedene Komponenten haben werde. Das bedeutet, jedes Objekt hat 60 Pointer/Indexwerte, von denen dann in der Regel 3 bis 5 genutzt werden, vielleicht auch mal 10, aber 50 werden meist total nutzlos leer sein. Ziemliche Speicherverschwendung aber best Performance und kein Downcast.... wahrscheinlich werde ich das, wenn das Prototypen der Komponenten abgeschlossen ist, mal ausprobieren.
Chromanoid hat geschrieben:@dot Meinst Du wirklich das Vermeiden von Downcasts bzw. die Nutzung von Typ-IDs, ist bei der Implementierung von völlig beliebigen Spielregeln eine sinnvolle Einschränkung? Hier ein Beispiel: Gib mir alle Entities in Bereich R (Abfrage an Physik-Komponente o.Ä.). Füge allen Entities mit einer Health-Komponente 10 Schadenspunkte zu. Alle Entities, die zusätzlich eine "Eiswesen"-Komponente haben, bekommen die Komponente "Vergiftet" aufgedrückt. Wenn schon die Komponente "Vergiftet" vorliegt, wird der Vergiftungsfaktor verdoppelt. Wenn das Entity eine "Charakter"-Komponente hat und ihr Name mit Z anfängt, gibt's 5 Extraschadenspunkte.
Schönes Beispiel, so ähnlich soll das am Ende ablaufen. :)

Nochmal Danke für die Antworten, über das Alles werde ich jetzt erstmal in Ruhe nachdenken.
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Template-Argument als Bedingung

Beitrag von Goderion »

Gibt es eine Möglichkeit, folgenden Quellcode eleganter zu lösen?

Code: Alles auswählen

inline EnumValueType _GetValueType(const int* pValue) { return 1; }
inline EnumValueType _GetValueType(const bool* pValue) { return 2; }
inline EnumValueType _GetValueType(const float* pValue) { return 3; }
inline EnumValueType _GetValueType(const double* pValue) { return 4; }

template <class T> int GetValueType(const T& Value)
{
	return _GetValueType(&Value);
}
Anwendungsbeispiel:

Code: Alles auswählen

int Value;
int ValueType = GetValueType(Value); // ValueType = 1
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: [C++] Template-Argument als Bedingung

Beitrag von Schrompf »

Du willst den Typen eines Parameters in einem Enum umwandeln. Da das theoretisch zur Compile Time feststeht, könntest Du Type Traits aufziehen. Die sind allerdings nicht eleganter oder hübscher. Finde ich jedenfalls.

Code: Alles auswählen

template <typename T> struct ValueType { constexpr EnumValueType type = Invalid; };
template <> struct ValueType<int> { constexpr EnumValueType type = Integer; };

template <typename T> 
constexpr EnumValueType GetValueType(T) { return ValueType<T>::type; }
Oder Du machst Deine Funktionen constexpr und belässt es, wie es ist.
Zuletzt geändert von Schrompf am 12.02.2018, 22:14, insgesamt 1-mal geändert.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Template-Argument als Bedingung

Beitrag von dot »

1) das Undefined Behavior fixen indem du statt dem reservierten Namen _GetValueType etwas verwendest was erlaubt ist... ;)


Abgesehen davon dass ich selbstverständlich wieder das Design hinterfragen würde ( ;) ), könntest du hier einfach explizit spezialisieren:

Code: Alles auswählen

template <class T> int GetValueType(const T& Value) = delete;

template <> inline int GetValueType<int>(const int&)
{
        return 1;
}

template <> inline int GetValueType<bool>(const bool&)
{
        return 2;
}

template <> inline int GetValueType<float>(const float&)
{
        return 3;
}

template <> inline int GetValueType<double>(const double&)
{
        return 4;
}
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Template-Argument als Bedingung

Beitrag von Goderion »

Vielen Dank für die Antworten!
Schrompf hat geschrieben:Du willst den Typen eines Parameters in einem Enum umwandeln. Da das theoretisch zur Compile Time feststeht, könntest Du Type Traits aufziehen. Die sind allerdings nicht eleganter oder hübscher. Finde ich jedenfalls.
Bei mir (VS 2017) muss ich ein static vor constexpr setzten, sonst meckert er. (siehe unten V2)
Schrompf hat geschrieben:Oder Du machst Deine Funktionen constexpr und belässt es, wie es ist.
Mmmmh.... (siehe unten V1)
dot hat geschrieben:1) das Undefined Behavior fixen indem du statt dem reservierten Namen _GetValueType etwas verwendest was erlaubt ist... ;)
_GetValueType ist reserviert? Im Link finde ich nix zu _GetValueType. Wie ist das gemeint?
Die _GetValueType-Funktionen bekommen aber so oder so noch einen anderen Namen, die sollen ja nicht direkt aufgerufen werden.
dot hat geschrieben:Abgesehen davon dass ich selbstverständlich wieder das Design hinterfragen würde ( ;) ), ...
:) Zum Generieren der Datenbanktabellen muss ich die Datentypen der Membervariablen bestimmen. Keine Ahnung wie ich das anders machen soll.

Code: Alles auswählen

// Alte Version
AddColumn(m_Variable, ValueTypeX);
// Neue Version
AddColumn(m_Variable);
dot hat geschrieben:... könntest du hier einfach explizit spezialisieren:
(siehe unten V3 und V4)

Ich habe jetzt 4 Versionen zur Auswahl:

Code: Alles auswählen

namespace GVT
{
	namespace V1
	{
		constexpr int _GetValueType(const int*) { return 1; }
		constexpr int _GetValueType(const bool*) { return 2; }
		constexpr int _GetValueType(const float*) { return 3; }
		constexpr int _GetValueType(const double*) { return 4; }

		template <class T> constexpr int GetValueType(const T& Value)
		{
			return _GetValueType(&Value);
		}
	}

	namespace V2
	{
		template <class T> struct ValueType { static constexpr int type = 0; };
		template <> struct ValueType<int> { static constexpr int type = 1; };
		template <> struct ValueType<bool> { static constexpr int type = 2; };
		template <> struct ValueType<float> { static constexpr int type = 3; };
		template <> struct ValueType<double> { static constexpr int type = 4; };

		template <class T> constexpr int GetValueType(const T&) { return ValueType<T>::type; }
	}

	namespace V3
	{
		template <class T> int GetValueType(const T&) = delete;

		template <> int GetValueType<int>(const int&) { return 1; }
		template <> int GetValueType<bool>(const bool&) { return 2; }
		template <> int GetValueType<float>(const float&) { return 3; }
		template <> int GetValueType<double>(const double&) { return 4; }
	}

	namespace V4
	{
		template <class T> constexpr int GetValueType(const T&) = delete;

		constexpr int GetValueType(const int&) { return 1; }
		constexpr int GetValueType(const bool&) { return 2; }
		constexpr int GetValueType(const float&) { return 3; }
		constexpr int GetValueType(const double&) { return 4; }
	}
}
V1: Übertrieben lange Fehlermeldung (alle Überladungen werden aufgezählt).
V2: Geht immer, egal was ich da übergebe. Sehe ich als Nachteil.
V3/V4: Gute Fehlermeldung und in der IDE wird der Funktionsaufruf rot unterstrichen.

Mein Favorit ist V4, wobei ich keine Ahnung habe, inwiefern sich V3 und V4 unterscheiden.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [C++] Template-Argument als Bedingung

Beitrag von dot »

Goderion hat geschrieben:
dot hat geschrieben:1) das Undefined Behavior fixen indem du statt dem reservierten Namen _GetValueType etwas verwendest was erlaubt ist... ;)
_GetValueType ist reserviert? Im Link finde ich nix zu _GetValueType. Wie ist das gemeint?
Der Link führt zu der Stelle im C++ Standard, die besagt dass alle Identifier die doppelte Underscores enthalten oder mit einem Underscore gefolgt von einem Großbuchstaben beginnen reserviert sind. _GetValueType fällt in diese Kategorie. Ein Programm das ein Ding mit einem solchen Identifier als Namen deklariert hat streng genommen autom. Undefined Behavior.
Goderion hat geschrieben:Ich habe jetzt 4 Versionen zur Auswahl: [...]
Ich dachte irgendwie dass dein GetValueType() aus irgendeinem Grund ein Template sein muss. Ansonsten:

Code: Alles auswählen

namespace GVT
{
	namespace V5
	{
		constexpr int GetValueType(const int&) { return 1; }
		constexpr int GetValueType(const bool&) { return 2; }
		constexpr int GetValueType(const float&) { return 3; }
		constexpr int GetValueType(const double&) { return 4; }
	}
}
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [C++] Template-Argument als Bedingung

Beitrag von Krishty »

Goderion hat geschrieben:V2: Geht immer, egal was ich da übergebe. Sehe ich als Nachteil.
Lass doch die Standard-Definition weg, dann gehen nur noch die Spezialisierungen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Template-Argument als Bedingung

Beitrag von Goderion »

Vielen Dank für die Antworten!
dot hat geschrieben:Der Link führt zu der Stelle im C++ Standard, die besagt dass alle Identifier die doppelte Underscores enthalten oder mit einem Underscore gefolgt von einem Großbuchstaben beginnen reserviert sind. _GetValueType fällt in diese Kategorie. Ein Programm das ein Ding mit einem solchen Identifier als Namen deklariert hat streng genommen autom. Undefined Behavior.
Oha, danke für den Hinweis. Benutze öfter _ um Funktionen zu kennzeichnen, die nicht direkt aufgerufen werden sollen oder in Klassen als "interne" Funktionen dienen.
dot hat geschrieben:Ich dachte irgendwie dass dein GetValueType() aus irgendeinem Grund ein Template sein muss. Ansonsten:

Code: Alles auswählen

namespace GVT
{
	namespace V5
	{
		constexpr int GetValueType(const int&) { return 1; }
		constexpr int GetValueType(const bool&) { return 2; }
		constexpr int GetValueType(const float&) { return 3; }
		constexpr int GetValueType(const double&) { return 4; }
	}
}
Das geht leider nicht. short wird z.B. zu int konvertiert. Das const muss weg, dann explodiert aber leider die Fehlermeldung wie bei V1.
Krishty hat geschrieben:Lass doch die Standard-Definition weg, dann gehen nur noch die Spezialisierungen.
Das funktioniert, aber die Fehlermeldung finde ich unnötig lang.

V4 bleibt mein Favorit. Wenn nichts dagegen spricht, werde ich das so umsetzen.
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: [C++] Template-Argument als Bedingung

Beitrag von DerAlbi »

Benutze öfter _ um Funktionen zu kennzeichnen, die nicht direkt aufgerufen werden sollen oder in Klassen als "interne" Funktionen dienen.
Dafür gibts private:,protected: und public:. struct ist per default (also nach "{" ) public, class ist per default private.
Das funktioniert mit deinen namespace-Konstruken allerdings nicht. Da würde ich mich an die STL halten: die versteckt Hilfsfunktionen in einem weiteren unter-namespace detail oder was auch immer.
Benutzeravatar
Goderion
Beiträge: 82
Registriert: 16.09.2012, 12:02

Re: [C++] Template-Argument als Bedingung

Beitrag von Goderion »

Danke für die Antwort!
DerAlbi hat geschrieben:Dafür gibts private:,protected: und public:. struct ist per default (also nach "{" ) public, class ist per default private.
So meinte ich das nicht. Bei internen Funktionen meine ich Methoden, die private sind, aber deren Name aufgrund ihrer "Tätigkeit/Funktion" sonst z.B. doppelt wären. Ein kleines Beispiel:

Code: Alles auswählen

	void AddElement(ClassListElement* pElement)
	{
		BeforeAddElement(pElement);
		Impl_AddElement(pElement);
		AfterAddElement(pElement);
	}

	void RemoveElement(ClassListElement* pElement)
	{
		BeforeRemoveElement(pElement);
		Impl_RemoveElement(pElement);
		AfterRemoveElement(pElement);
	}

	void MoveElementBack(ClassListElement* pElement)
	{
		BeforeRemoveElement(pElement);
		Impl_RemoveElement(pElement);
		Impl_AddElement(pElement);
	}
In dem obigen Beispiel gibt es zwei Funktionen, die ein Element einer Liste hinzufügen. Die erste Funktion AddElement ist public und führt noch zusätzliche Funktionen aus, z.B. zur Validierung. Die zweite Funktion, hier jetzt Impl_AddElement genannt, fügt das Element tatsächlich zur Liste hinzu, ohne weitere Prüfungen usw..

Da mir nichts besseres eingefallen ist, habe ich vor allen Funktionen, die mit einem Unterstrich begonnen haben, ein Impl gesetzt. Falls ich eine bessere Lösung finde, kann ich das ja einfach ersetzen.
DerAlbi hat geschrieben:Das funktioniert mit deinen namespace-Konstruken allerdings nicht. Da würde ich mich an die STL halten: die versteckt Hilfsfunktionen in einem weiteren unter-namespace detail oder was auch immer.
Ich habe aktuell keine Funktionen, auf die man nicht zugreifen soll, die nicht private sind, aber eine gute Idee mit dem Namespace.
Antworten