Typargumente in C++ (Typargumente in C++), Lektion, Seite 722505
https://www.purl.org/stefan_ram/pub/typargumente_c++ (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram
C++-Kurs

Konstruierte Typen in C++ 

Ein Programm wie das folgende hatten wir schon zuvor (28.3.) kennengelernt, darin ist der Typ von »p« durch das »auto« allerdings etwas versteckt.

main.cpp

#include <ostream>
#include <iostream>
#include <utility> /* ::std::pair */

int main()
{ auto p { ::std::make_pair( 0, 0 ) };
p.first = 8;
p.second = 4;
::std::cout << p.first << '\n';
::std::cout << p.second << '\n'; }

transcript
8
4

Der tatsächliche Typ von »p« ist etwas vereinfacht gesagt  »::std:pair« (also „Paar“).

Genauer gesagt ist der Typ »::std:pair< int, int >« (also „Paar von »int« und »int«“). Dies ist der Typ von Objekten, die aus zwei int-Werten bestehen. Diese beiden Typen des von »::std::make_pair« erzeugten Paares wurden durch die Typen der beiden Argumente von »::std::make_pair« festgelegt.

Da der Typ von »0« »int« ist, ist der Typ von »::std::make_pair( 0, 0 )« also »::std:pair< int, int >«.

Das Zeichen »<« bezeichnen wir in diesem Zusammenhang als „spitze Klammer auf“.

Das Zeichen »>« bezeichnen wir in diesem Zusammenhang als „spitze Klammer zu“.

main.cpp

#include <initializer_list>
#include <ostream>
#include <iostream>
#include <string>
#include <utility> /* ::std::pair */

using namespace ::std::literals;

int main()
{ ::std::pair< int, int >p{ ::std::make_pair( 0, 0 ) };
p.first = 8;
p.second = 4;
::std::cout << p.first << '\n';
::std::cout << p.second << '\n';
::std::cout << p.::std::pair<int,int>::second << '\n'; }

transcript
8
4
4

Nachdem wir nun die korrekte Typbezeichnung kennen, können wir den Name des Feldes »second« auch genauer als »::std::pair<int,int>::second« angeben, wie im obigen Programm zu sehen. Allerdings ist dies selten nötig.

Wir könnten genausogut ein Paar aus einem int-Wert und einem ::std::string-Wert anlegen.

main.cpp

#include <initializer_list>
#include <ostream>
#include <iostream>
#include <string>
#include <utility> /* ::std::pair */

using namespace ::std::literals;

int main()
{ ::std::pair< int, ::std::string >p{ ::std::make_pair( 0, ""s ) };
p.first = 8;
p.second = "abc"s;
::std::cout << p.first << '\n';
::std::cout << p.second << '\n'; }

transcript
8
abc

Natürlich ist auch in diesem Fall auch weiterhin das bequeme und sichere »auto« möglich. Da es automatisch den richtigen Typ festlegt, kann man bei der Schreibweise des Typs keine Fehler machen!

main.cpp

#include <ostream>
#include <iostream>
#include <string>
#include <utility> /* ::std::pair */

using namespace ::std::literals;

int main()
{ auto p{ ::std::make_pair( 0, ""s ) };
p.first = 8;
p.second = "abc"s;
::std::cout << p.first << '\n';
::std::cout << p.second << '\n'; }

transcript
8
abc

Wir nennen den Typ »::std::pair< int, ::std::string >« konstruiert, weil er aus mehreren Typen zusammengesetzt ist. Wir nennen alle Typangaben konstruiert, die spitze Klammern enthalten.

Typschablonen

Der Teil, welcher sich in »::std::pair< int, ::std::string >« vor der ersten spitzen Klammer befindet, also »::std::pair«, wird Typschablone  oder einfach Schablone  genannt.

Die einzelnen Angaben, welche sich in »::std::pair< int, ::std::string >« durch Komma getrennt innerhalb der spitzen Klammer befinden, nennen wir auch Schablonenargumente  oder Typargumente  (oder – kurz – Argumente , wenn keine Verwechslungen mit anderen Arten von Argumenten möglich sind).

Ein solches Typargument muß nicht selber ein Typ sein, auch einige andere Arten von Angaben, wie beispielsweise ganze Zahlen, sind erlaubt.

Im Falle von »::std::pair< int, ::std::string >« ist also »::std::pair« eine Typschablone, »int« ist das erste Typargument  und »::std::string« das zweite.

Während »::std::pair« eine Typschablone  ist, ist »::std::pair< int, ::std::string >« ein Typ.

Die Dokumentation von Typschablonen

Auszug aus der Dokumentation (2015) (vereinfacht und überarbeitet)

// defined in header <utility>

namespace std
{

template< class T1, class T2 >
struct pair;

}

»namespace std« leitet eine geschweifte Klammer ein. Alle Namen, die direkt in jener geschweiften Klammer dokumentiert werden, gehören zum Namensraum »::std«. Hier wird ein Name direkt in der geschweiften Klammer deklariert, nämlich »pair«. »pair« gehört also zum Namensraum »::std«, weswegen wir »::std::pair« schreiben.

»struct pair;« alleine würde bedeuten, daß es einen Typ »pair« gibt, der nach »#include <utility>« verwendet werden kann. Es wäre eine Proklamation eines Typs. Das Wort »struct« kennzeichnet einen Verbundtyp  (Strukturtyp), also einen Typ, dessen Objekte Einträge enthalten können.

»template< class T1, class T2 > struct pair;« ist eine Proklamation einer Typschablone. Es bedeutet, daß sich erst ein Typ ergibt, wenn zwei Typargumente in spitzen Klammern hinter »pair« geschrieben wurden. Dies wird durch »template< class T1, class T2 >« ausgedrückt. Dabei stehen die beiden Namen »T1« und »T2« für jene beiden Typen. Solche Namen einer Schablone werden Typparameter  genannt. Trotz der Verwendung des Wortes »class« vor »T1« und »T2« stehen diese beiden Typparameter für Typen und nicht etwa nur für Klassen!

Die Dokumentation sagt uns also insgesamt, daß es im Namensraum »::std« eine Typschablone »pair« mit zwei Typparametern »T1« und »T2« gibt, die nach der Inklusion des Köpfers »<utility>« zur Verfügung steht.

Solch eine Typschablone muß in der zuvor beschriebenen Weise verwendet werden, um zu einem Typ zu kommen. Das heißt, hinter »::std::pair« müssen zwei kommagetrennte Typangaben, die Typargumente, in spitzen Klammern folgen. Für jeden Typparameter muß ein Typargument angegeben werden.

In »::std::pair< int, ::std::string >« gehört das erste Typargument »int« also zum Typparameter »T1« und das zweite Typargument »::std::string« gehört zum zweiten Typparameter »T2«.

Welche Bedeutung  ein Typ hat, der bei einem konstruierten Typ in den spitzen Klammern steht, ist nicht  allgemein bestimmt, sondern wird immer erst durch die Dokumentation der Typschablone vor den spitzen Klammern festgelegt.

Die Dokumentation von Komponenten

Wir hatten die Dokumentation der Schablone »::std::pair« oben etwas verkürzt. Hier zeigen wir nun eine um die Komponenten erweiterte Dokumentation.

Auszug aus der Dokumentation (2015) (vereinfacht und überarbeitet)

// defined in header <utility>
namespace std
{

template < class T1, class T2 > struct pair
{ T1 first;
T2 second; }; }

In der folgende Dokumentation finden wir jetzt auch die Einträge von Objekten mit dem Typ »::std::pair< T1, T2 >« angegeben: Der Typ von »first« ist »T1« und der Typ von »second« »T2« – hierbei stehen »T1« und »T2« als Platzhalter für die beiden Typargument des Typs »::std::pair< T1, T2 >«.

Dies besagt, daß beispielsweise bei einem Objekt »o« vom Typ »::std::pair< int, double >« der Typ von »o.first« »int« und der Typ von »o.second« »double« ist.

Bezeichnungen

Offizielle Bezeichnungen

Die offiziell korrekte  Bezeichnung für einen konstruierten Typ lautet “a type that is a class template specialization ” („Ein Typ, der eine Klassenschablonenspezialisierung ist“).

Inoffizielle Bezeichnungen

Inoffiziell  wird ein konstruierter Typ manchmal auch als „Schablonenklasse “ (“template class ”) bezeichnet.

Man beachte den Unterschied zwischen einer Schablonenklasse  und einer Klassenschablone : Eine Schablonenklasse ist auf Typargumente angewendete Klassenschablone, durch die eine Klasse ausgedrückt wird. Beispielsweise ist »::std::pair« eine Klassenschablone und »::std::pair< int, double >« eine Schablonenklasse.

Das veraltete Buch „Annotated Reference Manual “ definierte “template class ” (Seite 343).

There are people who make semantic distinctions between the terms class template and template class. I don't; that would be too subtle: please consider those terms interchangeable. Similarly, I consider function template interchangeable with template function.” (The C++ Programming Language, 4th edition, 23.2.1 Defining a Template ).

»::std::basic_string<char>«

Die Schablone »::std::basic_string« kann man sich als eine Verallgemeinerung der Klasse »::std::string« vorstellen. Bei dieser Verallgemeinerung ist es möglich, den Typ der einzelnen Zeichen der Zeichenfolge festzulegen. Jener Typ ist normalerweise »char«. Tatsächlich ist »::std::string« nur eine Abkürzung für den konstruierten Typ »::std::basic_string<char>«.

main.cpp

#include <initializer_list>
#include <ostream>
#include <iostream>
#include <string>

using namespace ::std::literals;

int main()
{ ::std::basic_string< char >s{ "mno"s };
::std::cout << s << '\n'; }

transcript
mno

Dokumentationsort

In der C++ -Norm wird die Schablone »::std::basic_string« mit ihren Funktionseinträgen, wie beispielsweise »clear()«, ausführlich dokumentiert. Die Klasse »::std::string« wird dann hingegen nur kurz als »::std::basic_string<char>« definiert. Sucht man die Dokumentation der Einträge der Klasse »::std::string« in der C++ -Norm, findet man sie also unter der Dokumentation der Schablone »::std::basic_string«!

Wir verwenden im folgenden nun die folgenden neue Schreibweise der Proklamation der Klasse »::std::string«.

Darin wird mit »using string = basic_string< char >;« ausgedrückt, daß es im Standardnamensraum eine Klasse »::std::string« gibt, die man erhält, indem man die Schablone »basic_string« auf den Typ »char« anwendet (das heißt, überall wo dort »charT« steht, kann man gedanklich »char« einsetzen).

Einen konstruierten Typ, wie »::std::basic_string< char >« nennen wir auch eine Anwendung  einer Schablone, hier handelt es sich um die Anwendung der Schablone »::std::basic_string« auf den Typ »char«.

Proklamation der Klasse »::std::string« (neu, vereinfacht)

namespace std
{ using string = basic_string< char >;

template< class charT >
class basic_string
{ basic_string( basic_string const str );
basic_string( charT const * );
charT const * c_str() const;
charT const * data() const; }}

Proklamation der Klasse »::std::string« (zuvor, vereinfacht; zum Vergleich)
namespace std
{ class string
{ string( string const str );
string( char const * );
char const * c_str() const;
char const * data() const; }}

Diese neue Schreibweise der Proklamation hat den Nachteil, kompliziert und ungewohnt auszusehen; sie hat den Vorteil näher an der Original-Dokumentation von C++  in der ISO -Norm und damit näher an der Realität zu sein.

»using«

Eine using-Proklamation drückt in der Dokumentation immer aus, daß der linksstehende Typ als Name für den rechtsstehenden Typ gilt. Wir erhalten also den Typ »string«, indem wir die Schablone »basic_string« mit dem Typargument »char« für den Typparameter »charT« anwenden. Daraus ergibt sich, daß man die Dokumentation der Schablone »basic_string« als Dokumentation der Klasse »string« verwenden kann, wenn man darin »basic_string« gedanklich durch »string« und »charT« durch »char« ersetzt.

Eine using-Proklamation
using string = basic_string< char >;  

Traits

Eine Klasse kann, wie ein Namensraum, Einträge (Objekte, Funktionen und Typen) enthalten, die mit dem Bereichsauflösungsoperator »::« ausgedrückt werden können. Solche Einträge werden auch als statische Einträge  der Klasse bezeichnet.

Die konstruierten Beschreibungsklassen zu einem Typ enthalten statische Einträge, die den Typ beschreiben.

main.cpp

#include <iostream>
#include <ostream>
#include <type_traits>

int main()
{ ::std::cout << ::std::is_integral< int >::value << '\n';
::std::cout << ::std::is_floating_point< int >::value << '\n';
::std::cout << ::std::is_array< int >::value << '\n';
::std::cout << ::std::is_pointer< int >::value << '\n';
::std::cout << ::std::is_void< int >::value << '\n'; }

transcript
1
0
0
0
0

Eine Schablone erlaubt es, einen Typ offen zu lassen und erst beim Aufruf als Argument zu übergeben.

main.cpp

#include <iostream>
#include <ostream>
#include <type_traits>

template< typename T > void describe()
{ ::std::cout << ::std::is_integral< T >::value << '\n';
::std::cout << ::std::is_floating_point< T >::value << '\n';
::std::cout << ::std::is_array< T >::value << '\n';
::std::cout << ::std::is_pointer< T >::value << '\n';
::std::cout << ::std::is_void< T >::value << '\n' << '\n'; }

int main()
{ describe< int >();
describe< double >();
describe< int * >(); }

transcript
1
0
0
0
0

0
1
0
0
0

0
0
0
1
0

Der Typ kann dabei auch hergeleitet werden. Der Name eines nicht verwendeten Parameters kann entfallen.

main.cpp

#include <iostream>
#include <ostream>
#include <type_traits>

template< typename T > void describe( T const )
{ ::std::cout << ::std::is_integral< T >::value << '\n';
::std::cout << ::std::is_floating_point< T >::value << '\n';
::std::cout << ::std::is_array< T >::value << '\n';
::std::cout << ::std::is_pointer< T >::value << '\n';
::std::cout << ::std::is_void< T >::value << '\n' << '\n'; }

int main()
{ describe( 1 );
describe( 1.1 );
describe( nullptr ); }

transcript
1
0
0
0
0

0
1
0
0
0

0
0
0
0
0

Brüche mit C++ 

Manche Typen werden auch mit Zahlen konstruiert.

Die Brüche erlauben Brucharithmetik bei der Übersetzung. Dabei wird jeder Bruch durch einen Typ dargestellt. Diese Art der Datenverarbeitung mit Typen spielt in C++  heute eine große Rolle.

Die Typkonstruktion bereits während der Übersetzung des Programms, so daß bei Ablauf des Programms keine Zeit mehr dafür benötigt wird.

main.cpp

#include <iostream>
#include <ostream>
#include <ratio>

int main()
{ using r12 = ::std::ratio< 1, 2 >;
using r24 = ::std::ratio< 2, 4 >;
::std::cout << r12::num << "/" << r12::den << '\n';
::std::cout << r24::num << "/" << r24::den << '\n';

using sum = ::std::ratio_add< r12, r24 >::type;
::std::cout << sum::num << "/" << sum::den << '\n';

using m12 = ::std::ratio_multiply< r12, ::std::micro >::type;
::std::cout << m12::num << "/" << m12::den << '\n'; }

1/2
1/2
1/1
1/2000000

Übungsfragen

?   Typschablonen und Typen

Ist »::std::pair« eine Typschablone oder ein Typ?

?   Typschablonen und Typen (1)

Ist »::std::pair< int, int >« eine Typschablone oder ein Typ?

?   Typschablonen und Typen (2)

Ist »::std::string« eine Typschablone oder ein Typ?

?   Typen

Welchen Typ hat der Ausdruck »::std::make_pair( 0.0, 0.0 )«?

?   Komponenten

Welchen Typ und Wert hat »::std::make_pair( 0, "abc"s ).first«?

?   Komponenten (1)

Welchen Typ und Wert hat »::std::make_pair( 0, "abc"s ).second«?

?   Komponenten (2)

Angenommen der Ausdruck »p« hätte den Typ »::std::pair< double, int >«. Welchen Typ hätte dann der Ausdruck »p.first«?

Übungsaufgaben

/   Übungsaufgabe

Schreiben Sie ein Programm, in dem zwei Paare als Variablen angelegt und ausgegeben werden, die beide aus jeweils einem ::std::string-Objekt und einem double-Objekt (in dieser Reihenfolge) bestehen. Dabei soll der Typ einmal mit »auto« angegeben werden und einmal ohne »auto«.

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram722505 stefan_ram:722505 Typargumente in C++ Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd722505, slrprddef722505, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/typargumente_c++