[C++] Template-Argument als Bedingung

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.

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

Beitragvon Goderion » 08.02.2018, 16:51

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) 11-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: 75
Registriert: 16.09.2012, 12:02

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

Beitragvon Goderion » 12.02.2018, 21:56

Gibt es eine Möglichkeit, folgenden Quellcode eleganter zu lösen?
Code: Ansicht erweitern :: 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: Ansicht erweitern :: Alles auswählen
int Value;
int ValueType = GetValueType(Value); // ValueType = 1
Benutzeravatar
Goderion
 
Beiträge: 75
Registriert: 16.09.2012, 12:02

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

Beitragvon Schrompf » 12.02.2018, 23:13

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: Ansicht erweitern :: 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, 23:14, insgesamt 1-mal geändert.
Häuptling von Dreamworlds. Baut an was Neuem. Hilft nebenbei nur höchst selten an der Open Asset Import Library mit.
Benutzeravatar
Schrompf
Thomas Ziegenhagen
Moderator
 
Beiträge: 3640
Registriert: 26.02.2009, 00:44
Wohnort: Dresden
Benutzertext: Lernt nur selten dazu

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

Beitragvon dot » 12.02.2018, 23:13

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: Ansicht erweitern :: 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
dot
Michael Kenzel
Establishment
 
Beiträge: 1612
Registriert: 06.03.2004, 19:10

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

Beitragvon Goderion » 13.02.2018, 01:39

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: Ansicht erweitern :: 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: Ansicht erweitern :: 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
Goderion
 
Beiträge: 75
Registriert: 16.09.2012, 12:02

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

Beitragvon dot » 13.02.2018, 02:00

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: Ansicht erweitern :: 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
dot
Michael Kenzel
Establishment
 
Beiträge: 1612
Registriert: 06.03.2004, 19:10

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

Beitragvon Krishty » 13.02.2018, 03:54

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
Krishty
Establishment
 
Beiträge: 6196
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

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

Beitragvon Goderion » 13.02.2018, 09:45

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: Ansicht erweitern :: 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.
Benutzeravatar
Goderion
 
Beiträge: 75
Registriert: 16.09.2012, 12:02

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

Beitragvon DerAlbi » 13.02.2018, 18:03

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.
DerAlbi
Establishment
 
Beiträge: 180
Registriert: 20.05.2011, 05:37

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

Beitragvon Goderion » 13.02.2018, 18:29

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: Ansicht erweitern :: 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.
Benutzeravatar
Goderion
 
Beiträge: 75
Registriert: 16.09.2012, 12:02

Vorherige

Zurück zu Programmiersprachen, Quelltext und Bibliotheken

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 2 Gäste