Spaß mit Array-Typbezeichnern

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Spaß mit Array-Typbezeichnern

Beitrag von eXile »

Ich komme gerade von einer meiner Expedition aus den C++-Gebieten oberhalb des nördlichen Polarkreises persönlichen Erkenntniskreises zurück. Mein Logbuch:
  1. Krishtys schöne „Von-rechts-nach-links-Leseregel“ funktioniert leider nicht, sobald man Arrays benutzt:
    1. (&i)[3] ist eine Referenz auf ein 3-Array von ints,
    2. &(i[3]) ist ein 3-Array von Referenzen auf ints.
    Sollte man aber keine expliziten Array-Klammern im Typbezeichner haben, sollte die Leseregel aber wieder funktionieren.
  2. Die Notation von Funktionen, die eine Referenz oder einen Pointer auf ein Array zurückgeben, ist relativ archaisch:

    Code: Alles auswählen

    // Gibt eine Referenz auf ein 3-Array von ints zurück
    int (& getThreeInts()) [3]
    {
    	static int theInts[3] = { 1, 2, 3 };
    	return theInts;
    }
    Das ganze gibt's hier auch in Aktion zu sehen.
  3. Ich hatte mich schon einmal hier mit dem Zusammenspiel von Array-Referenzen und Templates befasst. Wie mir heute plötzlich aufgefallen ist, kann man das generalisieren und für eine Variante des „wie groß ist mein Array“-Problems benutzen:

    Code: Alles auswählen

    template <typename theType, size_t theSize>
    char (& AsArray(theType (&) [theSize])) [theSize];

    Code: Alles auswählen

    struct { int i; double d; } array[10];
    double * notAnArray;
     
    // Funktioniert.
    std::cout << sizeof(AsArray(array)) << std::endl;
    std::cout << sizeof(array) / sizeof(array[0]) << std::endl;
     
    // Funktioniert nicht; soll es auch nicht!
    // std::cout << sizeof(AsArray(notAnArray)) << std::endl;
     
    // Funktioniert; undefiniertes Verhalten. 
    std::cout << sizeof(notAnArray) / sizeof(notAnArray[0]) << std::endl;
    (Auch hier live und in Farbe). Im Gegensatz zu sizeof(…)/sizeof(…[0]) funktioniert es korrekterweise nicht mit Pointern. Es ist somit tatsächlich der Held den Gotham braucht typsicherer als das sizeof(…)/sizeof(…[0])-Konstrukt.
Irgendwelche Kommentare, Anmerkungen, Drohbriefe? ;)
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Spaß mit Array-Typbezeichnern

Beitrag von CodingCat »

eXile hat geschrieben:Die Notation von Funktionen, die eine Referenz oder einen Pointer auf ein Array zurückgeben, ist relativ archaisch:

Code: Alles auswählen

// Gibt eine Referenz auf ein 3-Array von ints zurück
int (& getThreeInts()) [3]
{
	static int theInts[3] = { 1, 2, 3 };
	return theInts;
}
Das ganze gibt's hier auch in Aktion zu sehen.
Deshalb typedef oder std::array, soweit möglich (C-Arrays lassen sich obendrein nicht kopieren).
eXile hat geschrieben:Ich hatte mich schon einmal hier mit dem Zusammenspiel von Array-Referenzen und Templates befasst. Wie mir heute plötzlich aufgefallen ist, kann man das generalisieren und für eine Variante des „wie groß ist mein Array“-Problems benutzen:
[...]
(Auch hier live und in Farbe). Im Gegensatz zu sizeof(…)/sizeof(…[0]) funktioniert es korrekterweise nicht mit Pointern. Es ist somit tatsächlich der Held den Gotham braucht typsicherer als das sizeof(…)/sizeof(…[0])-Konstrukt.
Worauf du hier hinaus willst, ist mir nicht ganz klar. Dieser ganze sizeof-Kram sollte eigentlich überhaupt nie mehr vorkommen. Warum die Standard-Bibliothek noch immer keine arraylen-Funktion anbietet, ist mir genauso ein Rätsel wie ihre kryptische, streng iteratorbasierte Algorithmenschnittstelle.

Code: Alles auswählen

template <class T, size_t N>
inline size_t arraylen(T (&)[N]) // constexpr?
{
   return N;
}

Code: Alles auswählen

// Funktioniert.
std::cout << arraylen(array) << std::endl;
 
// Funktioniert nicht; soll es auch nicht!
std::cout << arraylen(notAnArray) << std::endl;
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Spaß mit Array-Typbezeichnern

Beitrag von CodingCat »

C++11 hat geschrieben:The definition of a constexpr function shall satisfy the following constraints:
[...]
— each of its parameter types shall be a literal type;
[...]
Wenn das heißt, dass obige arraylen()-Funktion nicht als constexpr durchgeht, wäre dieses Feature aber sehr unglücklich ausgefallen. Mir ist ohnehin nicht klar, wieso es nicht Aufgabe des Compilers ist, inline-Funktionen einfach bei Aufruf nach Inlining auf constexpr-Konformanz zu prüfen (constexpr impliziert notwendigerweise immer inline).
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: Spaß mit Array-Typbezeichnern

Beitrag von eXile »

CodingCat hat geschrieben:Worauf du hier hinaus willst, ist mir nicht ganz klar.
Ich wollte eigentlich auf gar nichts hinaus. ;) Ich fand es nur interessant, dass obiger Code von mir tatsächlich funktioniert; ich finde ihn ganz hübsch, was natürlich eine viel zu subjektive Einschätzung ist. :)
CodingCat hat geschrieben:Dieser ganze sizeof-Kram sollte eigentlich überhaupt nie mehr vorkommen.
Naja, vielleicht doch, wenn auch nur sehr, sehr selten.

Machen wir halt ein ganz konkretes Beispiel. Stell dir mal vor, du hast ein Modell, das ein ganz bestimmtes Input-Layout hat, und CreateInputLayout erwartet die Größe des übergebenen D3D11_INPUT_ELEMENT_DESC-Arrays. Leider geht

Code: Alles auswählen

std::array<D3D11_INPUT_ELEMENT_DESC> inputLayout = {1, 2, 3};
nicht, sondern es muss

Code: Alles auswählen

std::array<D3D11_INPUT_ELEMENT_DESC, 3> inputLayout = {1, 2, 3};
heißen. An CreateInputLayout übergibt man die Array-Größe via inputLayout.size(). Das ist ja noch OK.

Angenommen, das ganz bestimmte Input-Layout des Models ändert sich: Ein Element fliegt raus. Schnell geändert, und Fehler gemacht:

Code: Alles auswählen

std::array<D3D11_INPUT_ELEMENT_DESC, 3> inputLayout = {1, 2};
Damit ist das Array noch immer 3 Elemente groß, und das letzte ist eben nullinitialisiert. Das Problem ist, dass man da eine Abhängigkeit drin hat, die man eigentlich nicht haben will.

Stattdessen schreiben wir

Code: Alles auswählen

D3D11_INPUT_ELEMENT_DESC inputLayout[] = {1, 2};
und übergeben als Array-Größe sizeof(asArray(inputLayout)). Keine Abhängigkeit mehr.

Zugegeben, dieses Beispiel funktioniert nur bei nicht-generischen Implementierungen (beispielsweise wenn ich mir das Input-Layout durch den Modell-Lader vorschreiben lasse, ist das dann ein vector); aber manchmal kommen eben auch so nicht-generische Implementierungen vor. Und ebenfalls zugegeben: Die Abhängigkeit ist extrem Code-lokal, also nicht sehr schlimm.
CodinCat hat geschrieben:Warum die Standard-Bibliothek noch immer keine arraylen-Funktion anbietet, ist mir genauso ein Rätsel wie ihre kryptische, streng iteratorbasierte Algorithmenschnittstelle.
Dass man das auch mit einem constexp-Template hinkriegt, ist klar. ;) Zweiten Kommentar noch nicht gelesen, Moment.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf hat geschrieben:The definition of a constexpr function shall satisfy the following constraints:
[…]
— its return type shall be a literal type;
— each of its parameter types shall be a literal type;
[…]
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf hat geschrieben:A type is a literal type if it is:
[…]
— a reference type referring to a literal type; or
— an array of literal type; or
[…]
Und da das formal eine Referenz auf ein Array ist (und damit ja auch nicht zu einem Pointer verflacht werden kann!) sollte das keine Probleme geben.

Und ja, man sollte so etwas wie arraylen in die STL aufnehmen.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Spaß mit Array-Typbezeichnern

Beitrag von CodingCat »

eXile hat geschrieben:Ich wollte eigentlich auf gar nichts hinaus. ;) Ich fand es nur interessant, dass obiger Code von mir tatsächlich funktioniert; ich finde ihn ganz hübsch, was natürlich eine viel zu subjektive Einschätzung ist. :)
Naja, diese Dopplung sizeof(AsArray(...)) erscheint mir nicht viel weniger fehleranfällig als sizeof(...) / sizeof(...). In beiden Fällen führt das intuitive Weglassen der zweiten Hälfte zu nicht zur Compile-Zeit erkennbarem Fehlverhalten. (Obendrein ist sizeof(AsArray(...)) intuitiv absolut unverständlich?! ;))
eXile hat geschrieben:
CodingCat hat geschrieben:Dieser ganze sizeof-Kram sollte eigentlich überhaupt nie mehr vorkommen.
Naja, vielleicht doch, wenn auch nur sehr, sehr selten. Machen wir halt ein ganz konkretes Beispiel. [...]
Das "überhaupt nie" bezog sich nicht auf std::array, sondern auf konsequentes arraylen() statt sizeof()-Gepfriemel.
eXile hat geschrieben:Stattdessen schreiben wir

Code: Alles auswählen

D3D11_INPUT_ELEMENT_DESC inputLayout[] = {1, 2};
und übergeben als Array-Größe sizeof(asArray(inputLayout)). Keine Abhängigkeit mehr.
Aber wieso sizeof(asArray(inputLayout))?! Stattdessen schreiben wir arraylen(inputLayout), noch eine Fehlerquelle weniger. ;) In den meisten Fällen ist constexpr ohnehin nicht notwendig.
eXile hat geschrieben:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf hat geschrieben:The definition of a constexpr function shall satisfy the following constraints:
[…]
— its return type shall be a literal type;
— each of its parameter types shall be a literal type;
[…]
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf hat geschrieben:A type is a literal type if it is:
[…]
— a reference type referring to a literal type; or
— an array of literal type; or
[…]
Und da das formal eine Referenz auf ein Array ist (und damit ja auch nicht zu einem Pointer verflacht werden kann!) sollte das keine Probleme geben.

Und ja, man sollte so etwas wie arraylen in die STL aufnehmen.
Höchst kurios, ich habe hier einen aktuellen Working Draft (N3337), in dem der Zusatz mit den Referenzen jeweils fehlt. Im (älteren?) C++11 Final Draft hingegen finde ich genau das, was du hier zitierst.

Nachtrag: Die Definition von "literal type" schließt im aktuellen Working Draft schon Referenzen auf Literaltypen mit ein, deshalb wurde der zitierte Paragraph entsprechend verkürzt.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: Spaß mit Array-Typbezeichnern

Beitrag von eXile »

CodingCat hat geschrieben:Naja, diese Dopplung sizeof(AsArray(...)) erscheint mir nicht viel weniger fehleranfällig als sizeof(...) / sizeof(...).
Außer natürlich für Pointer, aber das hatten wir ja schon.
CodingCat hat geschrieben:In beiden Fällen führt das intuitive Weglassen der zweiten Hälfte zu nicht zur Compile-Zeit erkennbarem Fehlverhalten.
CodingCat hat geschrieben:Obendrein ist sizeof(AsArray(...)) intuitiv absolut unverständlich?! ;)
CodingCat hat geschrieben:Stattdessen schreiben wir arraylen(inputLayout), noch eine Fehlerquelle weniger. ;)
Jajaja, die Benennung ist scheiße, und auch mit dem weglassen hast du recht. Ich wollte noch eine Variante, die auch zur Kompilierzeit funktioniert (und constexpr ist in Visual C++ ja noch nicht drin). Aber du hast recht, die Dopplung ist absolut unnötig und gehört weggekapselt in einer Funktion. ;)
CodingCat hat geschrieben:Das "überhaupt nie" bezog sich nicht auf std::array, sondern auf konsequentes arraylen() statt sizeof()-Gepfriemel.
Achso, ja, OK.
CodingCat hat geschrieben:In den meisten Fällen ist constexpr ohnehin nicht notwendig.
Als generelle Frage: Was sagt der Standard eigentlich zur Optimierung? Tangiert den das gar nicht, oder gibt es irgendwelche Garantien, dass kein Funktionsaufruf generiert wird? Ich nehme mal an, Visual C++ wird das ohne Probleme zur Kompilierungszeit auflösen.
Benutzeravatar
Krishty
Establishment
Beiträge: 8350
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Spaß mit Array-Typbezeichnern

Beitrag von Krishty »

Am Arsch – Visual C++ ist manchmal so dumm, dass ich nur noch in Großbuchstaben programmieren will. Für den Fall von arraylen klappt es definitiv, ja. Aber glaub ja nicht, dass die Funktion, die den Wert als Parameter erwartet, den als constexpr weiterbenutzt – sogar dann, wenn sie komplett geinlinet wird, ist das bloßes Glücksspiel.

Ich war ja vor ein paar Monaten nicht mehr aus dem Jammern rausgekommen, weil ich eine Funktion hatte, die einen String-Hash berechnet. Da war die String-Länge ebenfalls so aufgelöst; die Funktion wurde ein paar tausend Mal geinlinet; aber innendrin wurde nichts weiter von der Konstanten aufgelöst.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: Spaß mit Array-Typbezeichnern

Beitrag von CodingCat »

eXile hat geschrieben:Ich wollte noch eine Variante, die auch zur Kompilierzeit funktioniert (und constexpr ist in Visual C++ ja noch nicht drin). Aber du hast recht, die Dopplung ist absolut unnötig und gehört weggekapselt in einer Funktion. ;)
Wohl eher in ein Makro dann. Als Makro hat deine Variante tatsächlich Vorteile, weil sie prä-C++11 vollen Syntax-Komfort in Compile-Zeit- wie Laufzeitkontexten bietet.

Code: Alles auswählen

template <typename T, size_t N>
char (& deduce_same_extent_char_array_type(T (&)[N]) )[N];

#define arraylen(a) sizeof(deduce_same_extent_char_array_type(a))
;)

In prä-constexpr halb-sechzehntel-C++11-Compilern wie MSVC10+ gibt es btw. auch das etwas umständlichere std::extent<decltype(a)>::value für Compile-Zeit-Kontexte.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: Spaß mit Array-Typbezeichnern

Beitrag von eXile »

Krishty hat geschrieben:Am Arsch – Visual C++ ist manchmal so dumm, dass ich nur noch in Großbuchstaben programmieren will. Für den Fall von arraylen klappt es definitiv, ja. Aber glaub ja nicht, dass die Funktion, die den Wert als Parameter erwartet, den als constexpr weiterbenutzt – sogar dann, wenn sie komplett geinlinet wird, ist das bloßes Glücksspiel.
Grandios. Warum ist Visual C++ nur so retardiert. Das wirft ganze Generationen von Programmierern zurück. Unabhängig von Thema; es sind ja solche Sachen, die mein Blut zum Kochen bringen:
http://blogs.msdn.com/b/vcblog/archive/2012/06/15/10320846.aspx hat geschrieben:STL Bugs Fixed In Visual Studio 2012
The <initializer_list> header in the VC10 release candidate
Ach, und wie? Genau!
http://connect.microsoft.com/VisualStudio/feedback/details/533464 hat geschrieben:We've fixed it by deleting <initializer_list> from VC11.
:evil:
CodingCat hat geschrieben:In prä-constexpr halb-sechzehntel-C++11-Compilern wie MSVC10+ gibt es btw. auch das etwas umständlichere std::extent<decltype(a)>::value für Compile-Zeit-Kontexte.
Hervorragend! Damit hat sich der Thread sich tatsächlich für mich sehr gelohnt, da mir std::extend noch gänzlich unbekannt war. Danke!
Benutzeravatar
dot
Establishment
Beiträge: 1746
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Spaß mit Array-Typbezeichnern

Beitrag von dot »

std::extend kannte ich auch noch nicht. Ich frag mich allerdings, wieso std::extend für non-array Typen 0 liefert; Imo wäre die einzig vernünftige Lösung, das Template nur für Arrays zu spezialisieren und sonst einfach undefiniert zu lassen!?

Zum Thema InputLayout: Ich löse das normalerweise so, dass ich das Erzeugen eines InputLayout der jeweiligen Geometry Klasse überlasse (die, die die VertexBuffer hält):

Code: Alles auswählen

virtual com_ptr<ID3D11InputLayout> createInputLayout(device, shadersignature) const = 0;
Ist rein logisch imo dort am besten aufgehoben (wer die Buffer erzeugt und den Draw Call absetzt, kennt naturgemäß das exakte Layout; niemand sonst muss es kennen) und dann brauchts auch nur ein einfaches Array in der Funktion...
eXile hat geschrieben:Krishtys schöne „Von-rechts-nach-links-Leseregel“ funktioniert leider nicht, sobald man Arrays benutzt:
Die ist ja auch nur ein Spezialfall der richtigen Regel, die da lautet: Von Innen nach Außen ;)
Antworten