Hypercell ein ] Hypercell aus ] Zeige Navigation ] Verstecke Navigation ]
c++.de  
   
Forentreff 2012     
Bücher-Shop mit Amazon (Buchkategorien)C++ : Referenzen zu C++ : C++ Builder : Visual C++ : C# : Java : Spieleprogrammierung : Systemprogrammierung Linux : Software-Entwicklung : .NET : Compilertechnik : Algorithmen & Datenstrukturen : Objektorientierung : Entwurfsmuster : UML : eXtreme Programming : Scrum : Projektmanagement : Software-Testing : Datenbanken : Tom DeMarco : Dilbert : User Friendly
C/C++ Forum :: Die Artikel ::  Einführung in die Programmierung mit Templates     Zeige alle Beiträge auf einer Seite Auf Beitrag antworten
Autor Nachricht
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 21:59:41 02.12.2005   Titel:   Einführung in die Programmierung mit Templates            Zitieren

In diesem Artikel versuche ich, einen kleinen Einblick in die Welt der generischen Programmierung mit Templates in C++ zu geben.

Inhalt:

  • 1 Einführung
  • 2 Die Compiler-Frage
  • 3 Definition von Templates
    • 3.1 Funktions-Templates
    • 3.2 Klassen-Templates
  • 4 Übergabe von Argumenten
  • 5 Überladen (Spezialisierung) von Funktions-Templates
  • 6 Vollständige/Partielle Spezialisierung von Klassen-Templates
  • 7 Das Schlüsselwort export
  • 8 Zum Schluss...


1 Einführung

Wem ging das nicht schon mal so: Eine (ähnliche, ja fast gleiche) Funktion oder Klasse musste mehrfach implementiert werden, weil wir sie für verschiedene Typen einsetzen wollten. Das Paradebeispiel sind Container-Klassen wie Listen: Einmal brauchen wir eine für int, dann eine für std::string und schließlich noch eine für unsere eigenen Klassen. Jedesmal eine Liste speziell für einen Typ zu schreiben, das wäre sehr zeitaufwändig und auch mühsam, abgesehen davon würden sich wahrscheinlich Fehler einschleichen, da viel mit Copy & Paste gearbeitet würde. Glücklicherweise bietet uns C++ aber ein Werkzeug an, mit dem wir "typunabhängig" programmieren können: Templates! Praktisch die gesamte C++-Standardbibliothek besteht aus Templates, angefangen bei std::string über std::vector bis zu den vielen Algorithmen wie std::copy oder std::find.

2 Die Compiler-Frage

Der immer noch sehr weitverbreitete und veraltete "VC++ 6"-Compiler ist leider nicht besonders gut für die (insbesondere fortgeschrittene) Template-Programmierung geeignet, für ein vernünftiges Arbeiten ist mindestens der VC 7.1 notwendig. Mit einem aktuellen g++ ist man auch auf der sicheren Seite.

Normale Templates wie Container sind für den VC++ 6 kein Problem, aber bei komplizierteren Deklarationen streikt er ziemlich schnell. Folgende, von evilissimo vorgeschlagene, Template-Klasse wird der VC++ 6 mit vielen Fehlermeldungen quittieren, obwohl der Code korrekt ist (im Extremfall kann der Compiler abstürzen):
C/C++ Code:
template < template < typename A , typename B , template < typename D > class C > class A >
struct BreakCompilerBack{};
C/C++ Code:
template < template < typename A , typename B , template < typename D > class C > class A >
struct BreakCompilerBack{};
C/C++ Code:
template < template < typename A , typename B , template < typename D > class C > class A >
struct BreakCompilerBack{};

Im Übrigen ist selbst der sehr gute g++ nicht unverwundbar. Es gibt einige Sachen, die der Compiler einfach (noch) nicht verträgt. Gerade SFINAE (Substitution failure is not an error) ist sehr tödlich für einige Compiler.

Wir empfehlen das VS 2008 Express Edition zu benutzen. Beide lassen sich nach einer Registrierung kostenlos bei Microsoft herunterladen. Gerade als Anfänger hat man keinen Grund, noch mit dem veralteten VC++ 6 zu beginnen, sondern sollte gleich mit einer neueren, besseren Version in die C++-Programmierung einsteigen. Die neuen Compiler werden sich auch abseits des Template-Schlachtfelds positiv mit schnellerem und besserem Code bemerkbar machen.
Ich habe die Beispiele alle mit dem g++ 3.3.6 problemlos kompilieren können. Man sollte sich im Übrigen von den "umfangreichen" Fehlermeldungen des Compilers bei Templates nicht einschüchtern lassen, auch wenn sie zu Beginn kaum lesbar erscheinen, mit der Zeit gewöhnt man sich daran.

3 Definition von Templates

Und los geht's: Um dem Compiler mitzuteilen, dass man ein Template definieren möchte, bedient man sich folgendem Präfix, welches einer Funktion oder Klasse vorangestellt wird:
C/C++ Code:
template <class T>
//oder: template <typename T>
C/C++ Code:
template <class T>
//oder: template <typename T>
C/C++ Code:
template <class T>
//oder: template <typename T>

T stellt einen Parameter mit einem beliebigen Typ dar, und obwohl hier das Schlüsselwort class steht, kann man auch char oder double einsetzen. Das Schlüsselwort typename ist gleichwertig mit class, allerdings kann man die Verwendung von beiden wie folgt einteilen: typename wird verwendet, wenn ein built-in oder eine Klasse als Parameter kommen kann, class wird benutzt, wenn ausschließlich Klassen erwartet werden. Diese Einteilung dient nur der Übersichtlichkeit und hat sonst keine Auswirkungen.

Selbstverständlich kann man auch mehrere Template-Parameter angeben:
C/C++ Code:
//Zwei Parameter, einer vom Typ T und einer vom Typ U
template <class T, class U>
...

template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
...
C/C++ Code:
//Zwei Parameter, einer vom Typ T und einer vom Typ U
template <class T, class U>
...

template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
...
C/C++ Code:
//Zwei Parameter, einer vom Typ T und einer vom Typ U
template <class T, class U>
...

template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
...

Für "nicht-Typ-Parameter", also built-ins, gelten folgende Einschränkungen:

    1. Sie dürfen nicht verändert werden
    2. Sie dürfen nur ganzzahlig sein

Im Übrigen werden built-ins als Konstanten behandelt, auf diesem Wege könnten wir z.B. bei einem Array<T, int> Klassen-Template die Größe festlegen.

Es ist jedoch möglich, Referenzen oder Zeiger auf Gleitpunkt-Typen als Parameter anzugeben:
C/C++ Code:
template <class T, float &f>
...
C/C++ Code:
template <class T, float &f>
...
C/C++ Code:
template <class T, float &f>
...

Außerdem kann man den Parametern, wie gewohnt, Default-Werte geben:
C/C++ Code:
//FastCopy ist irgendeine Klasse
template <class T=FastCopy, int number=10>
...
C/C++ Code:
//FastCopy ist irgendeine Klasse
template <class T=FastCopy, int number=10>
...
C/C++ Code:
//FastCopy ist irgendeine Klasse
template <class T=FastCopy, int number=10>
...

Hierbei gelten die gleichen Regeln wie bei normalen Default-Parametern:

    1. Wenn ein Parameter einen Default-Wert bekommt, so müssen alle nachfolgenden Parameter einen bekommen
    2. Wird bei der Instanzierung ein Argument weggelassen, so müssen alle nachfolgenden Argumente weggelassen werden


3.1 Funktions-Templates

Früher, als die Gummistiefel noch auch Holz waren ;) , war min ein äußerst beliebtes und bekanntes Makro, um den kleineren von zwei Werten herauszufinden:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

#define
MIN(a,b) ((a<b)? a:b)

using namespace std;

int main (int argc, char **argv) {
  int x=5,y=6;
 
  int z = MIN(x,y);
  cout<<z<<'\n';  //Gibt 5 aus
  return EXIT_SUCCESS;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

#define
MIN(a,b) ((a<b)? a:b)

using namespace std;

int main (int argc, char **argv) {
int x=5,y=6;

int z = MIN(x,y);
cout<<z<<'\n'; //Gibt 5 aus
return EXIT_SUCCESS;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

#define
MIN(a,b) ((a<b)? a:b)

using namespace std;

int main (int argc, char **argv) {
  int x=5,y=6;
 
  int z = MIN(x,y);
  cout<<z<<'\n';  //Gibt 5 aus
  return EXIT_SUCCESS;
};

Das war in C vielleicht noch gut, aber in C++ haben wir Templates, um solche Dinge sauber zu implementieren (die STL enthält bereits eine Template-Funktion namens min):
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

int main (int argc, char **argv) {
  int x=5, y=6;
 
  //Explizite Instanzierung (siehe "Übergabe von Argumenten"):
  int z = minimum<int>(x,y);
  std::cout<<z<<'\n';

  //Funktioniert auch für chars:
  char a = 'a', b = 'b';

  //Implizite Instanzierung (siehe "Übergabe von Argumenten"):
  std::cout<<minimum(a,b)<<'\n';  //Gibt a aus

  return EXIT_SUCCESS;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
return a < b ? a:b;
};

int main (int argc, char **argv) {
int x=5, y=6;

//Explizite Instanzierung (siehe "Übergabe von Argumenten"):
int z = minimum<int>(x,y);
std::cout<<z<<'\n';

//Funktioniert auch für chars:
char a = 'a', b = 'b';

//Implizite Instanzierung (siehe "Übergabe von Argumenten"):
std::cout<<minimum(a,b)<<'\n'; //Gibt a aus

return EXIT_SUCCESS;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

int main (int argc, char **argv) {
  int x=5, y=6;
 
  //Explizite Instanzierung (siehe "Übergabe von Argumenten"):
  int z = minimum<int>(x,y);
  std::cout<<z<<'\n';

  //Funktioniert auch für chars:
  char a = 'a', b = 'b';

  //Implizite Instanzierung (siehe "Übergabe von Argumenten"):
  std::cout<<minimum(a,b)<<'\n';  //Gibt a aus

  return EXIT_SUCCESS;
};

Das Funktions-Template minimum hat zwei Parameter vom Typ Referenz auf const-T und als Rückgabewert ebenfalls eine Referenz auf const-T. Was T ist bzw. später mal sein wird, das interessiert uns nicht. Das braucht nur der Aufrufer zu wissen.

Der Maschinencode für ein Funktions-Template wird bei der ersten Instanzierung für einen Typ erzeugt, bei der Definition selber wird nichts erzeugt. So wird im obigen Beispiel zuerst eine Funktion für den Typ int erzeugt, und dann noch eine weitere für den Typ char. Vereinfacht gesagt geht der Compiler hin und setzt für jedes T den von uns gewählten Typ ein.

Vom Compiler werden Templates zweimal auf Fehler überprüft: zuerst beim Kompilieren der Template-Definition und dann noch einmal bei der Instanzierung. Beim ersten Drübergehen werden typunabhängige Fehler (z.B. Syntaxfehler) erkannt. Fehler, die vom Typ abhängen, z.B. ein fehlender Operator des Typs, werden dann beim zweiten Mal angezeigt.

3.2 Klassen-Templates

Da es möglich ist, Funktions-Templates zu bilden, muss es auch möglich sein, ein Klassen-Template zu erstellen. Am Beispiel der Klasse Pair (die STL enthält bereits ein Klassen-Template namens pair), die ein Wertepaar darstellt, werden wir uns dies anschauen:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include
<string>

//Diesmal zwei Parameter, U ist per default gleich T
template <typename T, typename U=T>
struct Pair {
  //Zwei Datenelemente
  T first;
  U second;

  Pair(const T &a, const U &b) : first(a), second(b) {}
  Pair(const Pair &p) : first(p.first), second(p.second) {}
  ~Pair() {}

  Pair& operator=(const Pair&);
};

//Definition außerhalb der Klasse:
template <typename T, typename U>
Pair<T,U>& Pair<T,U>::operator=(const Pair<T,U> &p) {
  if (this == &p)
    return *this;

  first = p.first;
  second = p.second;
  return *this;
};

int main (int argc, char **argv) {
  //Wir bilden ein Paar vom Typ float:
  Pair<float> floatPair(5.1,8.9);
  std::cout<<floatPair.first<<'\t'<<floatPair.second<<'\n';

  //Erster Parameter ist ein string, der zweite ein int:
  Pair<std::string, int> mixPair("zwanzig", 20);
  std::cout<<mixPair.first<<'\t'<<mixPair.second<<'\n';
 
  return EXIT_SUCCESS;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include
<string>

//Diesmal zwei Parameter, U ist per default gleich T
template <typename T, typename U=T>
struct Pair {
//Zwei Datenelemente
T first;
U second;

Pair(const T &a, const U &b) : first(a), second(b) {}
Pair(const Pair &p) : first(p.first), second(p.second) {}
~Pair() {}

Pair& operator=(const Pair&);
};

//Definition außerhalb der Klasse:
template <typename T, typename U>
Pair<T,U>& Pair<T,U>::operator=(const Pair<T,U> &p) {
if (this == &p)
return *this;

first = p.first;
second = p.second;
return *this;
};

int main (int argc, char **argv) {
//Wir bilden ein Paar vom Typ float:
Pair<float> floatPair(5.1,8.9);
std::cout<<floatPair.first<<'\t'<<floatPair.second<<'\n';

//Erster Parameter ist ein string, der zweite ein int:
Pair<std::string, int> mixPair("zwanzig", 20);
std::cout<<mixPair.first<<'\t'<<mixPair.second<<'\n';

return EXIT_SUCCESS;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include
<string>

//Diesmal zwei Parameter, U ist per default gleich T
template <typename T, typename U=T>
struct Pair {
  //Zwei Datenelemente
  T first;
  U second;

  Pair(const T &a, const U &b) : first(a), second(b) {}
  Pair(const Pair &p) : first(p.first), second(p.second) {}
  ~Pair() {}

  Pair& operator=(const Pair&);
};

//Definition außerhalb der Klasse:
template <typename T, typename U>
Pair<T,U>& Pair<T,U>::operator=(const Pair<T,U> &p) {
  if (this == &p)
    return *this;

  first = p.first;
  second = p.second;
  return *this;
};

int main (int argc, char **argv) {
  //Wir bilden ein Paar vom Typ float:
  Pair<float> floatPair(5.1,8.9);
  std::cout<<floatPair.first<<'\t'<<floatPair.second<<'\n';

  //Erster Parameter ist ein string, der zweite ein int:
  Pair<std::string, int> mixPair("zwanzig", 20);
  std::cout<<mixPair.first<<'\t'<<mixPair.second<<'\n';
 
  return EXIT_SUCCESS;
};

Bei der ersten Instanzierung von Pair wird zuerst der Maschinencode aller Methoden generiert (die Methoden der Klasse Pair sind im Grunde nur Funktions-Templates), und erst dann das Objekt floatPair aufgebaut. Der Maschinencode von mixPair unterscheidet sich im Übrigen von dem Maschinencode von floatPair!
Hier wird auch deutlich, dass wir mit Templates den Maschinencode nicht reduzieren, aber sehr wohl das Duplizieren von Sourcecode vermeiden können.
Außerdem sollte man sich vor Augen führen, dass Templates, bedingt durch ihre Natur, sehr statische Konstrukte sind.

Verwendung finden Templates auch bei der Implementierung von Container-Klassen wie einem Stack, gerade hier kann man durch Verwendung von Templates richtig Zeit einsparen, anstatt einen IntStack, einen CharStack usw. zu schreiben, schreibt man eine Template-Klasse:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename T>
class Stack {
public:
  Stack(size_t);
  Stack(const Stack&);
  ~Stack();
 
  void push(const T&);
  T pop();

  const T& peek() const;

  void clear();
  bool empty() const;

  Stack& operator=(const Stack&);

private:
  T *arr;
  size_t sz, tip;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename T>
class Stack {
public:
Stack(size_t);
Stack(const Stack&);
~Stack();

void push(const T&);
T pop();

const T& peek() const;

void clear();
bool empty() const;

Stack& operator=(const Stack&);

private:
T *arr;
size_t sz, tip;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename T>
class Stack {
public:
  Stack(size_t);
  Stack(const Stack&);
  ~Stack();
 
  void push(const T&);
  T pop();

  const T& peek() const;

  void clear();
  bool empty() const;

  Stack& operator=(const Stack&);

private:
  T *arr;
  size_t sz, tip;
};


4 Übergabe von Argumenten

Bei der Übergabe von Argumenten an Templates gibt es einige Regeln, die man unbedingt kennen sollte:

Die Typen der Argumente müssen exakt mit den Typen der Template-Parameter übereinstimmen, bei einer impliziten Instanzierung findet nicht einmal eine, sonst übliche, implizite Konvertierung wie z.B. von int nach long statt:
C/C++ Code:
long l=7;
int i=8;

//minimum Template von oben, ein long und ein int
cout<<minimum(l,i)<<'\n';  //Implizit: Eeeh, Fehler!
cout<<minimum<long>(l,i)<<'\n';  //Explizit: Funktioniert!
C/C++ Code:
long l=7;
int i=8;

//minimum Template von oben, ein long und ein int
cout<<minimum(l,i)<<'\n'; //Implizit: Eeeh, Fehler!
cout<<minimum<long>(l,i)<<'\n'; //Explizit: Funktioniert!
C/C++ Code:
long l=7;
int i=8;

//minimum Template von oben, ein long und ein int
cout<<minimum(l,i)<<'\n';  //Implizit: Eeeh, Fehler!
cout<<minimum<long>(l,i)<<'\n';  //Explizit: Funktioniert!

Bei der expliziten Instanzierung kann man den gewünschten Typ angeben und es wird eine Typumwandlung durchgeführt. Die explizite Instanzierung ist ebenfalls notwendig, wenn der Typ nicht als Parameter einer Funktion erscheint, sondern nur intern verwendet wird:
C/C++ Code:
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
template <typename T>
void foo() {
  T tmp;
  //...
};

//Explizite Instanzierung notwendig!
foo<int>();
C/C++ Code:
1
2
3
4
5
6
7
8
template <typename T>
void foo() {
T tmp;
//...
};

//Explizite Instanzierung notwendig!
foo<int>();
C/C++ Code:
1
2
3
4
5
6
7
8
template <typename T>
void foo() {
  T tmp;
  //...
};

//Explizite Instanzierung notwendig!
foo<int>();

Für Template-Argumente gibt es wiederum einige Einschränkungen, diese gelten aber nur für built-ins:

    1. Ist der Parameter ein Zeiger, so dürfen nur Adressen mit globalem Geltungsbereich übergeben werden
    2. Ist der Parameter eine Referenz, so dürfen nur Objekte mit globalem oder statischem Geltungsbereich übergeben werden
    3. Ist der Parameter weder Referenz noch Zeiger, so dürfen nur konstante Werte übergeben werden


5 Überladen (Spezialisierung) von Funktions-Templates

Manchmal passiert es, dass ein Template für einen bestimmten Typ kein vernünftiges Ergebnis liefert oder eine spezialisierte Funktion effizienter arbeiten könnte. Unser minimum-Template funktioniert z.B. für int ganz ausgezeichnet, aber was ist mit C-Strings? Da würde unser Template versagen bzw. einfach den C-String mit der kleineren Adresse zurückgeben, nicht gerade das, was wir wollen:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include
<cstring>

using namespace std;

//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

//Überladung für C-Strings
inline const char* minimum(const char *str1, const char *str2) {
  return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};

int main (int argc, char **argv) {
  //Aufruf der "normalen" Template-Funktion
  cout<<minimum(8,10)<<'\n';  

  //Aufruf der überladenen Funktion
  cout<<minimum("HALLO", "hallo")<<'\n';

  return EXIT_SUCCESS;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include
<cstring>

using namespace std;

//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
return a < b ? a:b;
};

//Überladung für C-Strings
inline const char* minimum(const char *str1, const char *str2) {
return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};

int main (int argc, char **argv) {
//Aufruf der "normalen" Template-Funktion
cout<<minimum(8,10)<<'\n';

//Aufruf der überladenen Funktion
cout<<minimum("HALLO", "hallo")<<'\n';

return EXIT_SUCCESS;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include
<cstring>

using namespace std;

//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

//Überladung für C-Strings
inline const char* minimum(const char *str1, const char *str2) {
  return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};

int main (int argc, char **argv) {
  //Aufruf der "normalen" Template-Funktion
  cout<<minimum(8,10)<<'\n';  

  //Aufruf der überladenen Funktion
  cout<<minimum("HALLO", "hallo")<<'\n';

  return EXIT_SUCCESS;
};

Wir haben jetzt die Funktion minimum überladen, um unser Ziel zu erreichen.
Es gibt aber noch eine andere Möglichkeit: Das Spezialisieren des Funktions-Templates. Das Spezialisieren unterscheidet sich vom Überladen, denn die überladene Funktion von oben ist eine ganz gewöhnliche Funktion. Spezialisiert man die Funktion jedoch, hat man eine "echte" Template-Funktion:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
//Generische Implementierung
template <typename T>
void func(T param) {
  //mach was mit param
};

//Spezialisierte Implementierung für int
template < >
void func(int param) {
  //mach was mit param
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
//Generische Implementierung
template <typename T>
void func(T param) {
//mach was mit param
};

//Spezialisierte Implementierung für int
template < >
void func(int param) {
//mach was mit param
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
//Generische Implementierung
template <typename T>
void func(T param) {
  //mach was mit param
};

//Spezialisierte Implementierung für int
template < >
void func(int param) {
  //mach was mit param
};


Wer sich näher für die exakten Unterschiede interessiert, dem empfehle ich Abschnitt 14.5.5 fortfolgend im C++ Standard. Dort sind auch einige Beispiele aufgeführt. In diesem Zusammenhag ist der Artikel Why not specialize function templates? ebenfalls sehr interessant. Vielen Dank an tommie-lie für diesen Link.

Es kann durchaus vorkommen, dass eine ganze Reihe an Funktionen mit dem gleichen Namen umherschwirrt. Der Compiler geht bei der Auswahl der passenden Funktion folgendermaßen vor:

    1. Findet der Compiler eine normale Funktion, die er ohne Typumwandlung aufrufen kann, so wird diese aufgerufen
    2. Wenn es ein spezialisiertes Template (zusätzlich zu der generischen Implementierung) gibt, dann nimmt der Compiler immer dieses, gibt es mehrere spezialisierte Templates, so wählt er das am meisten Spezialisierte aus
    3. Konnte keine passende Funktion gefunden werden, so werden normale Funktionen überprüft, bei denen Typumwandlungen zum Erfolg führen

Wird keine oder mehrere passende Funktion(en) gefunden, ist dies ein Fehler.

Nun ist die überladene Version von minimum aber eine gewöhnliche Funktion, d.h. es wird sobald der Header von mehreren Sourcefiles eigebunden wird, Linkerfehler wegen mehrfacher Definition von minimum hageln.
Um dies zu vermeiden, sollten wir die Funktion in eine Implementationdatei auslagern. Des Weiteren könnten wir sie inline oder static klassifizieren oder wir betten sie in einen (anonymen) namespace ein. Die namespace-Variante hat allerdings den Nachteil, dass minimum in mehreren Objektdateien präsent ist.

6 Vollständige/Partielle Spezialisierung von Klassen-Templates

Manchmal ist es wichtig, dass eine Template-Klasse bei einer gewissen Kombination der Typ-Parameter etwas ganz Spezielles tut, dies erreicht man durch die partielle bzw. vollständige Spezialisierung von Template-Klassen. Partielle Spezialisierung von Funktionen ist nicht möglich.

Bevor wir die partielle Spezialisierung sehen, zuerst eine vollständige Spezialisierung der Pair-Klasse:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

template <typename T, typename U=T>
struct Pair {
  //wie oben
};

struct MyServer {};
struct MyClient {};

//MyServer und MyClient sind hier irgendwelche Klassen, für die das Template
//vollständig spezialisiert wird:

template <>
struct Pair<MyServer, MyClient> {
  Pair() { std::cout<<"Vollstaendige Spezialisierung aufgerufen!"<<'\n'; }
  //...
};

int main(int argc, char **argv) {
  Pair<MyServer, MyClient> myPair;  //Instanzierung des Templates

  return EXIT_SUCCESS;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

template <typename T, typename U=T>
struct Pair {
//wie oben
};

struct MyServer {};
struct MyClient {};

//MyServer und MyClient sind hier irgendwelche Klassen, für die das Template
//vollständig spezialisiert wird:

template <>
struct Pair<MyServer, MyClient> {
Pair() { std::cout<<"Vollstaendige Spezialisierung aufgerufen!"<<'\n'; }
//...
};

int main(int argc, char **argv) {
Pair<MyServer, MyClient> myPair; //Instanzierung des Templates

return EXIT_SUCCESS;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

template <typename T, typename U=T>
struct Pair {
  //wie oben
};

struct MyServer {};
struct MyClient {};

//MyServer und MyClient sind hier irgendwelche Klassen, für die das Template
//vollständig spezialisiert wird:

template <>
struct Pair<MyServer, MyClient> {
  Pair() { std::cout<<"Vollstaendige Spezialisierung aufgerufen!"<<'\n'; }
  //...
};

int main(int argc, char **argv) {
  Pair<MyServer, MyClient> myPair;  //Instanzierung des Templates

  return EXIT_SUCCESS;
};

Wenn wir jetzt also die Klasse Pair mit diesen speziellen Parametern, nämlich MyServer und MyClient, aufrufen, dann wird das spezialisierte Template benutzt. Andernfalls wird die generische Implementierung verwendet.

Kommen wir nun zu der partiellen Spezialisierung, bei der es (mal wieder) ein paar Regeln zu beachten gilt:

    1. Ein Klassen-Template kann sowohl vollständig als auch partiell spezialisiert werden
    2. Eine Member-Methode eines Klassen-Templates kann nur vollständig spezialisiert werden
    3. Eine Funktion auf Namespace-Ebene kann nicht partiell spezialisiert werden, wobei die Überladung (siehe oben) als eine Art Ersatz angesehen werden kann.


Und so kann partielle Spezialisierung aussehen:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Spezialisierung für MyServer und ein beliebiges U
template <typename U>
struct Pair<MyServer, U> {
  //...
};

//Spezialisierung für ein beliebiges T und MyClient
template <typename T>
struct Pair<T, MyClient> {
  //...
};

int main(int argc, char **argv) {
  //Aufruf der ersten Spezialisierung
  Pair<MyServer, UnknownClient> firstPair;

  //Aufruf der zweiten Spezialisierung
  Pair<SomeServer, MyClient> secondPair;

  //Aufruf der generischen Implementation
  Pair<SomeServer, UnknownClient> thirdPair;

  return 0;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Spezialisierung für MyServer und ein beliebiges U
template <typename U>
struct Pair<MyServer, U> {
//...
};

//Spezialisierung für ein beliebiges T und MyClient
template <typename T>
struct Pair<T, MyClient> {
//...
};

int main(int argc, char **argv) {
//Aufruf der ersten Spezialisierung
Pair<MyServer, UnknownClient> firstPair;

//Aufruf der zweiten Spezialisierung
Pair<SomeServer, MyClient> secondPair;

//Aufruf der generischen Implementation
Pair<SomeServer, UnknownClient> thirdPair;

return 0;
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Spezialisierung für MyServer und ein beliebiges U
template <typename U>
struct Pair<MyServer, U> {
  //...
};

//Spezialisierung für ein beliebiges T und MyClient
template <typename T>
struct Pair<T, MyClient> {
  //...
};

int main(int argc, char **argv) {
  //Aufruf der ersten Spezialisierung
  Pair<MyServer, UnknownClient> firstPair;

  //Aufruf der zweiten Spezialisierung
  Pair<SomeServer, MyClient> secondPair;

  //Aufruf der generischen Implementation
  Pair<SomeServer, UnknownClient> thirdPair;

  return 0;
};

Das Spielchen kann man ziemlich weit treiben, denn der Algorithmus zur Bestimmung des am meisten spezialisierten Templates ist sehr exakt und wählt die Implementierung mit der höchsten Übereinstimmung aus.

7 Das Schlüsselwort export

Dieser Abschnitt wurde von 7H3 N4C3R beigesteuert, an dieser Stelle ein Dankeschön von mir.

Bei den vorangegangenen Beispielen war es noch nicht notwendig, aber wenn man größere Klassen oder Bibliotheken implementiert, dann möchte man die Schnittstelle in eine .hpp-Datei und die Implementation in eine .cpp-Datei schreiben.
Die Motivation dahinter ist, dass jede Änderung an einer Headerdatei dazu führt, dass all der Code neu kompiliert werden muss, der diese Headerdatei benutzt. Gerade in Projekten mit vielen Dateien löst das große Neu-Kompilier-Wellen aus, die ziemlich lange dauern können.

Leider geht das bei Templates so nicht. Das liegt daran, dass das Template erst überall dort in den Code eingesetzt wird, wo es auch verwendet wird. Vorher ist es "nur ein Stück Text", erst beim Einsetzen bekommt es seine Bedeutung. Genau das verursacht aber das oben beschriebene Verhalten vom Neu-Kompilieren.

Der eine oder andere hat nun aber vielleicht schon vom Schlüsselwort export gehört, welches nun doch genau diese Trennung ermöglichen soll. In die Headerdatei schreibt man vor das template einfach nur das Wörtchen export, und schon kann man eine .cpp-Datei mit der Implementation füllen.

Stopp. Soweit so gut, das war die Theorie. In der Praxis steht es um export aber völlig anders. Zuerst: Kaum ein Compiler unterstützt es überhaupt. Lediglich die Front-Ends, die auf den EDG-Compiler (Edison Design Group) aufsetzen, beherrschen es. Das ist im Wesentlichen der Comeau-Compiler. Das und die Tatsache, dass die Entwicklungszeit für dieses Compilerfeature drei Mannjahre betrug, sollte einen stutzig machen (eine mittelgroße bis große Individualanwendung hat ca. zwei Mannjahre Entwicklungszeit). Was stimmt also mit export nicht?

Im Wesentlichen kann export nicht das halten, was man sich von der Trennung in .hpp- und .cpp-Datei verspricht.

Man könnte denken, dass bei einer Auslieferung einer selbst geschriebenen Bibliothek nur die .hpp-Datei mitgegeben werden muss und die Implementierung in der .cpp-Datei versteckt bleibt (als kompilierte .o-Datei zur Bibliothek dazugelinkt). Das ist nicht so. Die kurze Antwort ist, dass der Standard verlangt, dass das Template bei seiner Instanzierung vollständig (inklusive Implementation im Quellcode-Format) bekannt ist.

Auch ein anderer scheinbarer Vorteil ist nicht gegeben, nämlich dass durch das Auslagern der Implementation nicht mehr soviel Quellcode neu übersetzt werden muss. Das ist zwar so schon richtig, dafür muss aber die .cpp-Datei des Templates für jeden Datentyp, für den das Template instanziert wird, übersetzt werden. Die Abhängigkeiten, die durch das Verlagern der Implementation in die .cpp-Datei verschwinden, schlagen hinterrücks wieder zu. Denn sie sind nur versteckt, aber nicht aus der Welt.

Andere Nachteile von export sind in der Regel höhere Kompilierzeiten, auch wenn sie in der Theorie eigentlich sinken sollten. Auerdem haben EDG-basierte Compiler nur eine mögliche Implementierung von export. Das liegt daran, dass der Standard dieses Schlüsselwort nicht genau genug beschreibt. Es wäre möglich, dass andere Compilerhersteller export nach der Beschreibung im Standard korrekt implementieren, es sich aber überall unterschiedlich verhält und unterschiedlichen Code produziert.

Der wohl schlimmste Nachteil von export (dessen Erklärung hier im Detail wohl zu weit führt) ist, dass es die Bedeutung von definierten Sprachfeatures gefährlich verändert. Im Endeffekt muss man höllisch aufpassen, um mit export denselben Code zu schreiben wie ohne.

Deshalb das Fazit (zumindest für die nächste Zeit): Finger weg von export.

Es ist jedoch möglich, diese Einschränkung mit einem Trick zu umgehen: Wir inkludieren einfach eine .impl-Datei (normale Source-Datei mit .impl-Endung) in die .hpp-Datei:
C/C++ Code:
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
//stack.hpp
template <typename T>
class Stack {
  //wie oben, nur die Schnittstelle
};

//Achtung:
#include
"stack.impl"
C/C++ Code:
1
2
3
4
5
6
7
8
//stack.hpp
template <typename T>
class Stack {
//wie oben, nur die Schnittstelle
};

//Achtung:
#include
"stack.impl"
C/C++ Code:
1
2
3
4
5
6
7
8
//stack.hpp
template <typename T>
class Stack {
  //wie oben, nur die Schnittstelle
};

//Achtung:
#include
"stack.impl"

C/C++ Code:
//stack.impl
template<typename T>
inline bool Stack<T>::empty() const {
  return (tip==0) ? true : false;
};
//...
C/C++ Code:
//stack.impl
template<typename T>
inline bool Stack<T>::empty() const {
return (tip==0) ? true : false;
};
//...
C/C++ Code:
//stack.impl
template<typename T>
inline bool Stack<T>::empty() const {
  return (tip==0) ? true : false;
};
//...

Es ist zwar kein export, aber so kann man zumindest die Schnittstelle von der Implementation sauber trennen.

8 Zum Schluss...

Das war jetzt nur eine kleine Einführung, es gibt noch so vieles, was man mit Templates machen kann, von Policy-basiertem Klassendesign über Typlisten bis zu Objektfabriken. Templates können einem das Leben extrem erleichtern. Für einen tieferen Einstieg in die Materie empfehle ich "C++ Templates: The Complete Guide" von David Vandervoorde und Nicolai M. Josuttis und "Modernes C++ Design" von Andrei Alexandrescu . "Gehobenes Niveau", aber sehr lesenswert.


Zuletzt bearbeitet von GPC am 17:17:55 04.10.2008, insgesamt 11-mal bearbeitet
Konrad
Mitglied

Benutzerprofil
Anmeldungsdatum: 14.02.2004
Beiträge: 158
Beitrag Konrad Mitglied 03:10:14 27.12.2005   Titel:   Re: Einführung in die Programmierung mit Templates            Zitieren

Schöner Artikel. Gefällt mir gut. :)

GPC schrieb:
Das war jetzt nur eine kleine Einführung, es gibt noch so vieles, was man mit Templates machen kann, von Policy-basiertem Klassendesign über Typlisten bis zu Objektfabriken. Templates können einem das Leben extrem erleichtern. Für einen tieferen Einstieg in die Materie empfehle ich "Modernes C++ Design" von Andrei Alexandrescu. "Gehobenes Niveau", aber sehr lesenswert.


Modernes C++ Design ist wirklich ein sehr gutes Buch. Das Problem ist nur, dass zum verstehen diese Template-Einführung nicht reichen wird und Alexandrescu selbst nichts von den verwendeten C++ Techniken erklärt.
Deshalb würde ich empfehlen das folgende Buch noch vor Alexandrescus Buch zu lesen.
ISBN:0201734842
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 16:20:45 29.12.2005   Titel:              Zitieren

Das stimmt allerdings, wenn man nicht schon genug Vorwissen im Bereich Templates hat, kann einem Modernes C++ Design wirklich Schwierigkeiten machen. Ich werde deine Empfehlung in den Artikel einbauen.
hagman
Mitglied

Benutzerprofil
Anmeldungsdatum: 10.11.2005
Beiträge: 13
Beitrag hagman Mitglied 10:48:10 12.01.2006   Titel:              Zitieren

Sehr schöne Einführung :live:.

Vielleicht könnte man Punkt 6 noch ergänzen indem man erwähnt, dass man Spezialisierungen doch in eine gewöhnliche Implementationsdatei schreiben muss, wenn man sie nicht inline oder static deklariert, damit es keine Linkerfehler aufgrund mehrfacher Definitionen gibt.

_________________
Keep it simple: as simple as possible, but no simpler. (Albert Einstein)
Viele Grüße
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 14:58:58 15.01.2006   Titel:              Zitieren

Hallo,

hagman schrieb:
Sehr schöne Einführung :live:.

Vielen Dank :)

Zitat:
Vielleicht könnte man Punkt 6 noch ergänzen indem man erwähnt, dass man Spezialisierungen doch in eine gewöhnliche Implementationsdatei schreiben muss, wenn man sie nicht inline oder static deklariert, damit es keine Linkerfehler aufgrund mehrfacher Definitionen gibt.

Bei welchem Compiler tritt dieses Problem auf? Bei meinem g++ 3.3.6 kann ich dies bei folgendem Beispiel nicht nachvollziehen:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//min.hpp
#ifndef
MIN_HPP
#define
MIN_HPP

template <typename T>
const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

const char* minimum(const char *str1, const char *str2) {
  return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};

#endif
//MIN_HPP
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//min.hpp
#ifndef
MIN_HPP
#define
MIN_HPP

template <typename T>
const T& minimum(const T &a, const T &b) {
return a < b ? a:b;
};

const char* minimum(const char *str1, const char *str2) {
return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};

#endif
//MIN_HPP
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//min.hpp
#ifndef
MIN_HPP
#define
MIN_HPP

template <typename T>
const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

const char* minimum(const char *str1, const char *str2) {
  return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};

#endif
//MIN_HPP


C/C++ Code:
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
//main.cpp
#include
<iostream>
#include
"min.hpp"

int main() {
  std::cout<<minimum<int>(5,7)<<'\n'
           <<minimum("hallo","HALLO")<<'\n';
  return EXIT_SUCCESS;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
//main.cpp
#include
<iostream>
#include
"min.hpp"

int main() {
std::cout<<minimum<int>(5,7)<<'\n'
<<minimum("hallo","HALLO")<<'\n';
return EXIT_SUCCESS;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
//main.cpp
#include
<iostream>
#include
"min.hpp"

int main() {
  std::cout<<minimum<int>(5,7)<<'\n'
           <<minimum("hallo","HALLO")<<'\n';
  return EXIT_SUCCESS;
}


Oder meintest du etwas anderes?

Mfg

GPC
ness
Autor

Benutzerprofil
Anmeldungsdatum: 16.07.2004
Beiträge: 1165
Beitrag ness Autor 17:09:48 15.01.2006   Titel:              Zitieren

Und auch erwähnen sollte man, dass partielle spezialisierungen von klassentemplates im selben scope stattfinden müssen.
ISO 14882:2003 14.5.4p6 schrieb:

A class template partial specialization may be declared
or redeclared in any namespace scope in which its
definition may be defined (14.5.1 and 14.5.2).
Deshalb geht z.B.
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace foo
{
    template<class T, class U>
    struct s
    {
    }
}

namespace bar
{
    template<class T>
    struct foo::s<T, int>
    {
    }
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace foo
{
template<class T, class U>
struct s
{
}
}

namespace bar
{
template<class T>
struct foo::s<T, int>
{
}
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace foo
{
    template<class T, class U>
    struct s
    {
    }
}

namespace bar
{
    template<class T>
    struct foo::s<T, int>
    {
    }
}
nicht.

_________________
meine jabber ID: ness2@jabber.org | mein pgp key | mein blog
hagman
Mitglied

Benutzerprofil
Anmeldungsdatum: 10.11.2005
Beiträge: 13
Beitrag hagman Mitglied 20:42:31 15.01.2006   Titel:              Zitieren

Genau das meine ich. Hast du min.hpp im selben Programm noch in eine andere Übersetzunseinheit inkludiert? Das hätte ich vielleicht noch erwähnen sollen. Also ich habe dein Beispiel mal unverändert genommen und innerhalb eines Programms in zwei verschiedene Implementierungsdateien inkludiert. Folgende Linkerfehler habe ich erhalten:

VC 2003 und VC 2005 schrieb:
Error 1 error LNK2005: "char const * __cdecl minimum(char const *,char const *)" (?minimum@@YAPBDPBD0@Z) already defined in main.obj foo.obj
Error 2 fatal error LNK1169: one or more multiply defined symbols found E:\test\console\Debug\console.exe


gcc 4.0.2 schrieb:
g++ -c main.cpp
g++ -c min.cpp
g++ -c foo.cpp
g++ -o test main.o min.o foo.o
foo.o: In function `minimum(char const*, char const*)':
foo.cpp:(.text+0x0): multiple definition of `minimum(char const*, char const*)'
main.o:main.cpp:(.text+0x0): first defined here
collect2: ld returned 1 exit status
make: *** [test] Error 1


Das liegt wohl daran, dass minimum durch die Spezialisierung zu einer fertig implementierten, "gewöhnlichen" Funktion wird. Deshalb gelten für das spezialisierte minimum die Regeln wie für normale Funktionen auch: Definition in die Implementationsdatei oder als inline oder static spezifizieren.

_________________
Keep it simple: as simple as possible, but no simpler. (Albert Einstein)
Viele Grüße


Zuletzt bearbeitet von hagman am 20:44:56 15.01.2006, insgesamt 1-mal bearbeitet
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 21:55:28 17.01.2006   Titel:              Zitieren

Hallo (sorry für die lange Reaktionszeit),

hagman schrieb:
Genau das meine ich. Hast du min.hpp im selben Programm noch in eine andere Übersetzunseinheit inkludiert?
Das hätte ich vielleicht noch erwähnen sollen.

Yo, dann haben wir ein Problem mit unserem Freund, dem Linker. Aber das macht nichts, denn anstatt die Spezialisierung static zu machen (oder inline, was manchmal einfach nicht geht), setzen wir sie in einen (anonymen) namespace:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//min.hpp
#ifndef
MIN_HPP
#define
MIN_HPP

namespace {

template <typename T>
const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

const char* minimum(const char *str1, const char *str2) {
  return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};

};  //namespace

#endif
//MIN_HPP
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//min.hpp
#ifndef
MIN_HPP
#define
MIN_HPP

namespace {

template <typename T>
const T& minimum(const T &a, const T &b) {
return a < b ? a:b;
};

const char* minimum(const char *str1, const char *str2) {
return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};

}; //namespace

#endif
//MIN_HPP
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//min.hpp
#ifndef
MIN_HPP
#define
MIN_HPP

namespace {

template <typename T>
const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

const char* minimum(const char *str1, const char *str2) {
  return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};

};  //namespace

#endif
//MIN_HPP


Jetzt alles klar?

@ness
Auch deine Anmerkung wird Einzug im Artikel finden, danke dir für den Hinweis.

Mfg

GPC
ness
Autor

Benutzerprofil
Anmeldungsdatum: 16.07.2004
Beiträge: 1165
Beitrag ness Autor 20:24:54 18.01.2006   Titel:              Zitieren

naja, aber der anonyme namespace ist doch eine ziemlich "unsaubere" Lösung, finde ich. Entweder du wilst die funktionen inlinen, dann benutz das schlüsselwort, oder eine vollständig spezialisierte funktion gehört in eine implementierungsdatei.

_________________
meine jabber ID: ness2@jabber.org | mein pgp key | mein blog
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 17:19:18 19.01.2006   Titel:              Zitieren

Hallo,

ness schrieb:
naja, aber der anonyme namespace ist doch eine ziemlich "unsaubere" Lösung, finde ich. Entweder du wilst die funktionen inlinen, dann benutz das schlüsselwort, oder eine vollständig spezialisierte funktion gehört in eine implementierungsdatei.

das kann man jetzt halten, wie man will. Für gewöhnlich lagere ich die spezialisierte Funktion auch in eine Implementationsdatei aus, oft habe ich aber schon benannte Namensräume, und dann lass ich sie einfach im Header.

Btw. Was findest du, ist unsauber daran?

mfg

GPC
ness
Autor

Benutzerprofil
Anmeldungsdatum: 16.07.2004
Beiträge: 1165
Beitrag ness Autor 17:49:53 19.01.2006   Titel:              Zitieren

naja, weil du dann den code mehrmals (in jeder objektdatei) hast. In dem sinne kannst du fast alles in unbenannte namespace packen.

_________________
meine jabber ID: ness2@jabber.org | mein pgp key | mein blog
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 21:44:31 19.01.2006   Titel:              Zitieren

Hi,

ness schrieb:
naja, weil du dann den code mehrmals (in jeder objektdatei) hast. In dem sinne kannst du fast alles in unbenannte namespace packen.

stimmt, das ist bei größeren Funktionen/Klassen ein Argument. Werd mich über's WE mal ransetzen, sofern ich Zeit finde.

Mfg

GPC
KasF
Mitglied

Benutzerprofil
Anmeldungsdatum: 14.12.2004
Beiträge: 2378
Beitrag KasF Mitglied 00:33:05 20.01.2006   Titel:   Re: Einführung in die Programmierung mit Templates            Zitieren

Hi, cooler Artikel :live: ( nochmal Grundlagen aufgefrischt :D )

GPC schrieb:

Selbstverständlich kann man auch mehrere Template-Parameter angeben:
C/C++ Code:
//Zwei Parameter, einer vom Typ T und einer vom Typ U
template <class T, class U>
...

template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
...
C/C++ Code:
//Zwei Parameter, einer vom Typ T und einer vom Typ U
template <class T, class U>
...

template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
...
C/C++ Code:
//Zwei Parameter, einer vom Typ T und einer vom Typ U
template <class T, class U>
...

template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
...

Für "nicht-Typ-Parameter", also built-ins, gelten folgende Einschränkungen:

    1. Sie dürfen nicht verändert werden
    2. Sie dürfen nur ganzzahlig sein



Vielleicht sollte man hier auch noch erwähnen das built-ins als template paramterer als Konstanten betrachtet werden(Bei generischen Klassen).

Weil sie zur Compilezeit bekannt sind un man sie dadurch als Größenangabe für Array's verwenden kann und sie auch nicht verändert werden können.

_________________
Um C++ zu beherrschen muss man schon for( ;; ) fragen ob er genug Zeit für einen hat ...
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 14:35:16 20.01.2006   Titel:              Zitieren

Hallo,

Freak_Coder schrieb:
Hi, cooler Artikel :live: ( nochmal Grundlagen aufgefrischt :D )

Vielen Dank.

Zitat:

GPC schrieb:

Selbstverständlich kann man auch mehrere Template-Parameter angeben:
C/C++ Code:
//Zwei Parameter, einer vom Typ T und einer vom Typ U
template <class T, class U>
...

template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
...
C/C++ Code:
//Zwei Parameter, einer vom Typ T und einer vom Typ U
template <class T, class U>
...

template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
...
C/C++ Code:
//Zwei Parameter, einer vom Typ T und einer vom Typ U
template <class T, class U>
...

template <class T, int number> //Ein Parameter vom Typ T und einer vom Typ int
...

Für "nicht-Typ-Parameter", also built-ins, gelten folgende Einschränkungen:

    1. Sie dürfen nicht verändert werden
    2. Sie dürfen nur ganzzahlig sein



Vielleicht sollte man hier auch noch erwähnen das built-ins als template paramterer als Konstanten betrachtet werden(Bei generischen Klassen).

Dachte eigentlich, das sei klar, nachdem ich erwähnt habe, dass eine Auswertung zur Compilezeit erfolgt.

Zitat:
Weil sie zur Compilezeit bekannt sind un man sie dadurch als Größenangabe für Array's verwenden kann und sie auch nicht verändert werden können.

Aber ich pack's noch dazu, damit alle Klarheiten beseitigt sind.

Mfg

GPC
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 18:25:52 19.05.2006   Titel:   Re: Einführung in die Programmierung mit Templates            Zitieren

Guter Artikel :live:

Allerdings komme ich bei folgendem nicht ganz mit:
GPC schrieb:
Bei den vorangegangenen Beispielen war es noch nicht notwendig, aber wenn man größere Klassen oder Bibliotheken implementiert, dann möchte man die Schnittstelle in eine .hpp-Datei und die Implementation in eine .cpp-Datei schreiben.
Die Motivation dahinter ist, dass jede Änderung an einer Headerdatei dazu führt, dass all der Code neu kompiliert werden muss, der diese Headerdatei benutzt. Gerade in Projekten mit vielen Dateien löst das große Neu-Kompilier-Wellen aus, die ziemlich lange dauern können.

Leider geht das bei Templates so nicht. Das liegt daran, dass das Template erst überall dort in den Code eingesetzt wird, wo es auch verwendet wird. Vorher ist es "nur ein Stück Text", erst beim Einsetzen bekommt es seine Bedeutung. Genau das verursacht aber das oben beschriebene Verhalten vom Neu-Kompilieren.
Und genau das ist doch auch erwünscht, wenn ich ein Template ändere, oder nicht? Mir ist deswegen nicht ganz klar, warum man Teile der Template-Implementierung (die effektiv Teil des Templates sind) irgendwohin auslagern wollen könnte. Um auf das verwendete Beispiel einzugehen:
C/C++ Code:
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
//stack.hpp
template <typename T>
class Stack {
  //wie oben, nur die Schnittstelle
};

//Achtung:
#include
"stack.impl"
C/C++ Code:
1
2
3
4
5
6
7
8
//stack.hpp
template <typename T>
class Stack {
//wie oben, nur die Schnittstelle
};

//Achtung:
#include
"stack.impl"
C/C++ Code:
1
2
3
4
5
6
7
8
//stack.hpp
template <typename T>
class Stack {
  //wie oben, nur die Schnittstelle
};

//Achtung:
#include
"stack.impl"

C/C++ Code:
//stack.impl
template<typename T>
inline bool Stack<T>::empty() const {
  return (tip==0) ? true : false;
};
//...
C/C++ Code:
//stack.impl
template<typename T>
inline bool Stack<T>::empty() const {
return (tip==0) ? true : false;
};
//...
C/C++ Code:
//stack.impl
template<typename T>
inline bool Stack<T>::empty() const {
  return (tip==0) ? true : false;
};
//...
"Normalerweise" hätte man die Template-Funktion Stack<>::empty() ebenfalls im Header stack.hpp. Ändere ich nun den, wird jede .cpp-Datei neu kompiliert, die diesen Header eingefügt hat, weil sich ja etwas für den Quellcode interessantes geändert haben könnte. Mit der gezeigten Methode, eine Datei stack.impl zu benutzen, könnte ich stack.impl ändern und (sofern ich mein Build-System davon nicht benachrichtigt habe) mir einen Wolf kompilieren, ohne daß die Änderungen wirksam werden, weil das Änderungsdatum des Headers nicht neuer ist als das der kompilierten Objekte.
Genauso kann ich aber etwas an stack.hpp ändern und werde trotzdem den Rattenschwanz der stack.impl hinter mir herziehen, weil dann mein Build-System merkt: "Aha, Header neuer als Objekt, Kompilation anstoßen", der Compiler (Präprozessor) wird merken, daß da eine eigenartige stack.impl eingefügt wird und diese korrekterweise in den header einfügen, worauf dann der Compiler wieder den vollständigen Template-Code "sieht" und das Template so oft instanziiert, wie es nunmal nötig ist.
Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen). :confused:


Zuletzt bearbeitet von tommie-lie am 18:26:51 19.05.2006, insgesamt 1-mal bearbeitet
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 19:08:38 19.05.2006   Titel:   Re: Einführung in die Programmierung mit Templates            Zitieren

tommie-lie schrieb:
Guter Artikel :live:

Danke.

Zitat:
Und genau das ist doch auch erwünscht, wenn ich ein Template ändere, oder nicht?

Hat sich was geändert, soll es neu kompiliert werden, korrekt.

Zitat:
"Normalerweise" hätte man die Template-Funktion Stack<>::empty() ebenfalls im Header stack.hpp. Ändere ich nun den, wird jede .cpp-Datei neu kompiliert, die diesen Header eingefügt hat, weil sich ja etwas für den Quellcode interessantes geändert haben könnte. Mit der gezeigten Methode, eine Datei stack.impl zu benutzen, könnte ich stack.impl ändern und (sofern ich mein Build-System davon nicht benachrichtigt habe) mir einen Wolf kompilieren, ohne daß die Änderungen wirksam werden, weil das Änderungsdatum des Headers nicht neuer ist als das der kompilierten Objekte.

Die Trennung ist in dem Fall auch rein optischer Natur, denn am Ende inkludiere ich ja die cpp datei in die hpp Datei und damit bin ich gleich weit, wie wenn ich alles in die hpp Datei geschrieben hätte. Es ist nur eine Erleichterung der Übersicht.

Zitat:
Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen). :confused:

Es dient nur der Erhöhung der Übersichtlichkeit... hab ich das nicht auch irgendwo geschrieben? Egal.

MfG

GPC
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 19:18:59 19.05.2006   Titel:   Re: Einführung in die Programmierung mit Templates            Zitieren

GPC schrieb:
Zitat:
Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen). :confused:

Es dient nur der Erhöhung der Übersichtlichkeit... hab ich das nicht auch irgendwo geschrieben?
Klang für mich nicht so. In den ersten beiden Absätzen beschwerst du (oder 7H3 N4C3R) dich über die mitunter längeren Kompilierungszyklen, die entstehen, wenn man seine 100 Templates neu durch den Compiler jagt. Ich dachte, daß damit auch Augenmerk auf die Performance gelegt werden soll und die beschriebene Lösung die Performance verbessern soll.
Aber der letzte Satz des Absatzes sagt ja, daß man dmait die Schnittstelle von der Implementierung trennt, vielleicht hätte ich mehr auf den Satz als auf die ersten beiden Absätze hören sollen ;-)
Aber vielleicht sollte man über eine Warnung nachdenken, daß man sein Build-System irgendwie davon überzeugen sollte, daß stack.impl nun ebenfalls Teil des entsprechenden Moduls ist. Ich denke nicht, daß die GNU autotools da ohne Handarbeit automatisch drauf reagieren.
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 19:20:23 20.05.2006   Titel:   Re: Einführung in die Programmierung mit Templates            Zitieren

Hallo,

tommie-lie schrieb:
GPC schrieb:
Zitat:
Vielleicht liegt's nur an mir, aber mit verschließt sich der Sinn dieses Vorgehens (von der Erhöhung der Übersichtlichkeit mal abgesehen, aber es wird ja auf die Compile-Performance eingegangen). :confused:

Es dient nur der Erhöhung der Übersichtlichkeit... hab ich das nicht auch irgendwo geschrieben?
Klang für mich nicht so. In den ersten beiden Absätzen beschwerst du (oder 7H3 N4C3R) dich über die mitunter längeren Kompilierungszyklen, die entstehen, wenn man seine 100 Templates neu durch den Compiler jagt. Ich dachte, daß damit auch Augenmerk auf die Performance gelegt werden soll und die beschriebene Lösung die Performance verbessern soll.

schön wär's. Aber leider hat das performancetechnisch keine Auswirkungen.

Zitat:
Aber der letzte Satz des Absatzes sagt ja, daß man dmait die Schnittstelle von der Implementierung trennt, vielleicht hätte ich mehr auf den Satz als auf die ersten beiden Absätze hören sollen ;-)

kein Thema.

Zitat:
Aber vielleicht sollte man über eine Warnung nachdenken, daß man sein Build-System irgendwie davon überzeugen sollte, daß stack.impl nun ebenfalls Teil des entsprechenden Moduls ist. Ich denke nicht, daß die GNU autotools da ohne Handarbeit automatisch drauf reagieren.

äh, nein. Das tun sie nicht. denn die impl Datei ist ja nicht "direkt" am Build-Prozess beteiligt. Du siehst, die impl-Lösung zieht nen Rattenschwanz von Problemen nach sich. Ich pers. verzichte auf diese Möglichkeit und knall alles direkt in den Header.

MfG

GPC
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 19:27:11 20.05.2006   Titel:   Re: Einführung in die Programmierung mit Templates            Zitieren

GPC schrieb:
Du siehst, die impl-Lösung zieht nen Rattenschwanz von Problemen nach sich.
Ich weiß ;-)

GPC schrieb:
Ich pers. verzichte auf diese Möglichkeit und knall alles direkt in den Header.
Ich auch ;-)
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 19:29:51 20.05.2006   Titel:              Zitieren

haha, okay, dann sind wir uns ja einig... ;)
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 19:36:06 20.05.2006   Titel:              Zitieren

GPC schrieb:
haha, okay, dann sind wir uns ja einig... ;)
Jupp. Ich bin aufgrund des ersten Absatzes nur mit ganz anderen Vorstellung an diesen Abschnitt des Tutorials herangegangen und dachte, du würdest ernsthaft denken, daß dieses Verfahren die Performance verbessert. Da dem nicht so ist (beides, es verbessert du Performance nicht und du denkst auch nicht, daß es das tun würde), bleibt die Diskussion nur eine Anmerkung für alle weiteren Leser.


Zuletzt bearbeitet von tommie-lie am 19:36:29 20.05.2006, insgesamt 1-mal bearbeitet
__et_
Mitglied

Benutzerprofil
Anmeldungsdatum: 02.03.2006
Beiträge: 41
Beitrag __et_ Mitglied 16:07:56 03.08.2006   Titel:              Zitieren

Kann ich eigentlich in einem template Ausnahmebehandlung für bestimmte Typen einbauen. Also sowas in der Art

C/C++ Code:
template<typename T>
void f()
{
  if (typename(T)==TypeInt)
     T++;
  foo(T);
}
C/C++ Code:
template<typename T>
void f()
{
if (typename(T)==TypeInt)
T++;
foo(T);
}
C/C++ Code:
template<typename T>
void f()
{
  if (typename(T)==TypeInt)
     T++;
  foo(T);
}


danke
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 16:38:31 03.08.2006   Titel:              Zitieren

__et_ schrieb:
Kann ich eigentlich in einem template Ausnahmebehandlung für bestimmte Typen einbauen. Also sowas in der Art

C/C++ Code:
template<typename T>
void f()
{
  if (typename(T)==TypeInt)
     T++;
  foo(T);
}
C/C++ Code:
template<typename T>
void f()
{
if (typename(T)==TypeInt)
T++;
foo(T);
}
C/C++ Code:
template<typename T>
void f()
{
  if (typename(T)==TypeInt)
     T++;
  foo(T);
}


danke

Joah, du kannst es über Spezialisierungen (Abschnitt 5 bzw. 6 für Klassen) machen, aber mit ner if-Abfrage wird das nichts.

MfG

GPC
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 16:41:07 03.08.2006   Titel:              Zitieren

__et_ schrieb:
Kann ich eigentlich in einem template Ausnahmebehandlung für bestimmte Typen einbauen.
Gehen tut das sicherlich so ähnlich wie du es geschrieben hast, aber der übliche Weg ist es, seine Templates zu spezialisieren:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
template<typename T>
void foo(T arg1)
{
  // generische Implementierung für alle Typen
}

template<>
void foo(int arg1)
{
  // spezielle Implementierung die nur für ints gilt
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
template<typename T>
void foo(T arg1)
{
// generische Implementierung für alle Typen
}

template<>
void foo(int arg1)
{
// spezielle Implementierung die nur für ints gilt
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
template<typename T>
void foo(T arg1)
{
  // generische Implementierung für alle Typen
}

template<>
void foo(int arg1)
{
  // spezielle Implementierung die nur für ints gilt
}
Syntaktisch also genauso aufgebaut wie Überladen von Funktionen.
Beachte dazu aber auch den Artikel Why not specialize function templates?, die Templateauflösung von C++ ist nicht immer so intuitiv, wie du es dir denkst.

Edit: Zu spät...


Zuletzt bearbeitet von tommie-lie am 16:42:14 03.08.2006, insgesamt 1-mal bearbeitet
__et_
Mitglied

Benutzerprofil
Anmeldungsdatum: 02.03.2006
Beiträge: 41
Beitrag __et_ Mitglied 17:07:21 03.08.2006   Titel:              Zitieren

Danke :)
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 17:31:21 03.08.2006   Titel:              Zitieren

tommie-lie schrieb:
Beachte dazu aber auch den Artikel Why not specialize function templates?, die Templateauflösung von C++ ist nicht immer so intuitiv, wie du es dir denkst.

Wirklich ein sehr guter Artikel. Evtl. werd ich noch Links auf die wichtigsten Artikel von Sutter, Meyers usw. in meinen Artikel reinpacken, wäre bestimmt nicht verkehrt.

MfG

GPC
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 17:55:32 03.08.2006   Titel:              Zitieren

GPC schrieb:
Evtl. werd ich noch Links auf die wichtigsten Artikel von Sutter, Meyers usw. in meinen Artikel reinpacken, wäre bestimmt nicht verkehrt.
Naja, zuviel Sekundärliteratur kann auch erschlagen ;-)

Aber was mir jetzt erst in deinem Artikel auffällt:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

//Spezialisierung für C-Strings
inline const char* minimum(const char *str1, const char *str2) {
  return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
return a < b ? a:b;
};

//Spezialisierung für C-Strings
inline const char* minimum(const char *str1, const char *str2) {
return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

//Spezialisierung für C-Strings
inline const char* minimum(const char *str1, const char *str2) {
  return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};
Das ist keine Spezialisierung, das ist ein Überladen der Funktion. Und der Code ist auch nicht, wie im Artikel beschrieben, nicht ANSI-konform. Das ist er und ein moderner Compiler sollte keinerlei Fehlermeldung ausgeben (der GCC 4.0 gibt nichtmal eine Warnung aus). Die Deklaration mit und ohne den Modifier "template" macht einen semantischen Unterschied, nämlich den zwischen einer überladenen Funktion und einem überladenen Template.
Ich denke das sollte man nochmal irgendwie klarstellen.


Zuletzt bearbeitet von tommie-lie am 17:56:11 03.08.2006, insgesamt 1-mal bearbeitet
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 20:12:06 03.08.2006   Titel:              Zitieren

tommie-lie schrieb:

Aber was mir jetzt erst in deinem Artikel auffällt:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

//Spezialisierung für C-Strings
inline const char* minimum(const char *str1, const char *str2) {
  return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
return a < b ? a:b;
};

//Spezialisierung für C-Strings
inline const char* minimum(const char *str1, const char *str2) {
return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
//Ermittelt das Minimum aus a und b
template <typename T>
inline const T& minimum(const T &a, const T &b) {
  return a < b ? a:b;
};

//Spezialisierung für C-Strings
inline const char* minimum(const char *str1, const char *str2) {
  return ( (strcmp(str1,str2) < 0 ) ? str2 : str1 );
};
Das ist keine Spezialisierung, das ist ein Überladen der Funktion.

Steht im Satz drunter:;
GPC schrieb:
Eigentlich haben wir jetzt die Funktion minimum überladen, um unser Ziel, also die Spezialisierung, zu erreichen.


Zitat:
Und der Code ist auch nicht, wie im Artikel beschrieben, nicht ANSI-konform. Das ist er und ein moderner Compiler sollte keinerlei Fehlermeldung ausgeben (der GCC 4.0 gibt nichtmal eine Warnung aus).

IIRC macht auch der gcc 3.3.6 keinen Stress, dennoch ist der Code imho nicht ANSI konform, ich werde meine Quelle nachschlagen und posten, voraussichtlich morgen.

MfG

GPC
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 20:20:40 05.08.2006   Titel:              Zitieren

Wie versprochen, wenn auch verspätet, meine Quelle:

Prinz Peter, kirch-Prinz Ulla: C++ Lernen und professionell anwenden, 2. vollständig überarbeitete Auflage, Bonn 2002 (mitp), S. 760:
Zitat:

Im ANSI-Standard werden Template-Funktionen und "normale" Funktionen nicht unterschieden. Die Definition eines Funktions-Template und einer Funktion gleichen Namens, die auch aus dem Funktions-Template generiert werden könnte, führt daher zu einer Fehlermeldung des Compilers ("duplicate definition ...").

Deshalb sieht der ANSI-Standard eine eigene Syntax vor, um Spezialisierungen zu definieren:
C/C++ Code:
#include <cstring>

template<>
const char* min( const char* s1, const char* s2 )
{
    return ( (strcmp(s1, s2) < 0 ) ? s1: s2 );
}
C/C++ Code:
#include <cstring>

template<>
const char* min( const char* s1, const char* s2 )
{
return ( (strcmp(s1, s2) < 0 ) ? s1: s2 );
}
C/C++ Code:
#include <cstring>

template<>
const char* min( const char* s1, const char* s2 )
{
    return ( (strcmp(s1, s2) < 0 ) ? s1: s2 );
}



Ich gehe davon aus, dass ich mich darauf verlassen kann, obwohl ich in diesem Buch schon an anderer Stelle einen gravierenden Fehler entdeckt habe. Ich wollte das damals eigentlich noch im Standard nachschlagen, aber dann fehlte mir die Zeit dazu, so blieb es "ungeprüft" drin.

Dennoch bleibe ich solange bei meiner Meinung, bis mir jemand anderes das Gegenteil beweist, oder ich selber herausfinde, das ich falsch lag. Ich überlasse es dir, den Gegenbeweis zu erbringen.

MfG

GPC
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 21:20:40 05.08.2006   Titel:              Zitieren

Zitat:
Im ANSI-Standard werden Template-Funktionen und "normale" Funktionen nicht unterschieden.
Bei sehr strenger Auslegung soweit nix falsches.

Zitat:
Deshalb sieht der ANSI-Standard eine eigene Syntax vor, um Spezialisierungen zu definieren:
Richtig, das sind Template-Spezialisierungen (oder template overloading) im Gegensatz zu neuen Funktionsdeklarationen.


Nun, zum einen würde die Auflösungsregel ja überhaupt keinen Sinn ergeben, wenn dem tatsächlich so wäre. Du nennst folgende Auflösungsregeln:
Zitat:
1. Findet der Compiler eine normale Funktion, die er ohne Typumwandlung aufrufen kann, so wird diese aufgerufen
2. Wenn es ein spezialisiertes Template (zusätzlich zu der generischen Implementierung) gibt, dann nimmt der Compiler immer dieses, gibt es mehrere spezialisierte Templates, so wählt er das am meisten Spezialisierte aus
3. Konnte keine passende Funktion gefunden werden, so werden normale Funktionen überprüft, bei denen Typumwandlungen zum Erfolg führen
Denen widerspreche ich nicht, weil ich sie auch so kenne und weil sie auch in dem von mir verlinkten Sutter-Artikel stehen.
Regel 1 würde aber überhaupt keinen Sinn machen, wenn ich keine normale Funktion gleichen namens wie ein Funktionstemplate im gleichen Namespace deklarieren kann. Wenn ich aus jeder Funktion, die genauso heißt, durch voranstellen eines "template<>" ohnehin ein Funktionstemplate machen muss, dann würde der Compiler gar nicht erst nach einer "normalen Funktion, die er ohne Typumwandlung aufrufen kann" suchen, sondern würde nach einer passenden Templatespezialisierung suchen oder im Template den Typen eintragen.

Da ich mir den Standard nicht gekauft habe, greife ich auf ein Working PAper von 1996 zurück, in dem es in 14 Absatz 5 heißt:
Zitat:
The name of a class template shall not be declared to refer to any other template, class, function, object, enumeration, enumerator, namespace, or type in the same scope. Except that a function template can be overloaded either by (non-template) functions with the same name or by other function templates with the same name, a template name declared in namespace scope shall be unique in that namespace.
Mit anderen Worten: Ja, es gelten die gleichen grundsätzlichen Regeln wie bei gewöhnlichen, Nicht-Templatefunktionen, aber auch gewöhnliche Funktionen kann ich mit gleichem Namen aber unterschiedlicher Signatur überladen.

Weiterhin heißt es in Kapitel 14.8.3:
Zitat:
2 [Example:
Code:
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
          template<class T> T max(T a, T b) { return a>b?a:b; };

          void f(int a, int b, char c, char d)
          {
                  int m1 = max(a,b);  // max(int a, int b)
                  char m2 = max(c,d); // max(char a, char b)
                  int m3 = max(a,c);  // error: cannot generate max(int,char)
          }
Code:
1
2
3
4
5
6
7
8
template<class T> T max(T a, T b) { return a>b?a:b; };

void f(int a, int b, char c, char d)
{
int m1 = max(a,b); // max(int a, int b)
char m2 = max(c,d); // max(char a, char b)
int m3 = max(a,c); // error: cannot generate max(int,char)
}
Code:
1
2
3
4
5
6
7
8
          template<class T> T max(T a, T b) { return a>b?a:b; };

          void f(int a, int b, char c, char d)
          {
                  int m1 = max(a,b);  // max(int a, int b)
                  char m2 = max(c,d); // max(char a, char b)
                  int m3 = max(a,c);  // error: cannot generate max(int,char)
          }


3 Adding
Code:
          int max(int,int);
Code:
int max(int,int);
Code:
          int max(int,int);

to the example above would resolve the third call, by providing a
function that could be called for max(a,c) after using the standard
conversion of char to int for c.
Ich nehme nicht an, daß der Standard vorschlagen würde, eine gewöhnliche Funktion gleichen namens und gleicher Signatur(!) wie eine mögliche Templatefunktion einzuführen und dabei sogar implizite Typkonvertierung zu benutzen, wenn dies illegal wäre.


GPC schrieb:
tommie-lie schrieb:
Das ist keine Spezialisierung, das ist ein Überladen der Funktion.
Steht im Satz drunter:;
GPC schrieb:
Eigentlich haben wir jetzt die Funktion minimum überladen, um unser Ziel, also die Spezialisierung, zu erreichen.
Warum nennt man es dann im Kommentar im Code und im Absatz darüber "Spezialisierung"?
Der Standard kennt einen Unterschied zwischen Überladung und Spezialisierung, ich finde, den sollte man auch kennzeichnen und die Begriffe nicht synonym verwenden. Um so verwirrender ist der von dir zitierte Satz ja auch noch, wenn gleich darauf gesagt wird, daß das illegal wäre und man ein "template<>" davor setzen muss, was die Funktion templatisieren würde und somit eine echte Template-Spezialisierung hervorbringen würde.
Meiner MEinung nach wird da einfach zu sehr mit Begriffen durcheinandergewürfelt, die nicht durcheinandergewürfelt gehören, weil sie vollkommen unterschiedliche Auswirkungen auf das Verhalten des Compilers haben (siehe der Sutter-Artikel und deine Auflösungsregeln für Funktionsnamen, die am Anfang des Artikel-Abschnitts steht).
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 18:27:11 06.08.2006   Titel:              Zitieren

Hallo,

hab mal im Standard nachgesehen und bin auf folgende Punkte gestossen:
Zitat:

14.5.5 Function templates

A function template can be overloaded with other function templates and with normal (non-template) func-
tions. A normal function is not related to a function template (i.e., it is never considered to be a specializa-
tion), even if it has the same name and type as a potentially generated function template specialization. 130)

__________________
130) That is, declarations of non-template functions do not merely guide overload resolution of template functions with the same name.
If such a non-template function is used in a program, it must be defined; it will not be implicitly instantiated using the function tem-
plate definition.


14.5.5.1 Function template overloading
It is possible to overload function templates so that two different function template specializations have the
same type. [Example:
C/C++ Code:
       // file1.c                               // file2.c
       template<class T>                        template<class T>
            void f(T*);                                void f(T);
       void g(int* p) {                         void h(int* p) {
            f(p); // call                              f(p); // call
                  // f<int>(int*)                            // f<int*>(int*)

       }                                        }
C/C++ Code:
// file1.c // file2.c
template<class T> template<class T>
void f(T*); void f(T);
void g(int* p) { void h(int* p) {
f(p); // call f(p); // call
// f<int>(int*) // f<int*>(int*)

} }
C/C++ Code:
       // file1.c                               // file2.c
       template<class T>                        template<class T>
            void f(T*);                                void f(T);
       void g(int* p) {                         void h(int* p) {
            f(p); // call                              f(p); // call
                  // f<int>(int*)                            // f<int*>(int*)

       }                                        }
-end example]
Such specializations are distinct functions and do not violate the one definition rule (3.2).
The signature of a function template specialization consists of the signature of the function template and of
the actual template arguments (whether explicitly specified or deduced).

14.7 Template instantiation and specialization

An explicit specialization may be declared for a function template, a class template, a member of a class
template or a member template. An explicit specialization declaration is introduced by template<>.


Zitat:
Warum nennt man es dann im Kommentar im Code und im Absatz darüber "Spezialisierung"?

In der Überschrift steht klar Überladung (Spezialisierung), es muss wohl ein Flüchtigkeitsfehler gewesen sein.

Zitat:
Der Standard kennt einen Unterschied zwischen Überladung und Spezialisierung, ich finde, den sollte man auch kennzeichnen und die Begriffe nicht synonym verwenden. Um so verwirrender ist der von dir zitierte Satz ja auch noch, wenn gleich darauf gesagt wird, daß das illegal wäre und man ein "template<>" davor setzen muss, was die Funktion templatisieren würde und somit eine echte Template-Spezialisierung hervorbringen würde.

Nun, ich anerkenne, dass es einen Unterschied zw. der Überladung und der *echten* Spezialisierung gibt. Dieser Unterschied wird im Artikel tatsächlich nicht ausreichend dargestellt.

Ich werde den betreffenden Abschnitt abändern, sobald ich Zeit dafür finde. Okay?

MfG

GPC


Zuletzt bearbeitet von GPC am 18:27:30 06.08.2006, insgesamt 1-mal bearbeitet
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 19:43:37 06.08.2006   Titel:              Zitieren

GPC schrieb:
hab mal im Standard nachgesehen und bin auf folgende Punkte gestossen:
14.5 habe ich doch auch zitiert :p

GPC schrieb:
Okay?
Du musst mit mir nicht verhandeln, es ist dein Artikel. Ich wollte lediglich Fehler (oder was meiner Meinung nach welche sind) aufzeigen :)
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 14:23:38 07.08.2006   Titel:              Zitieren

tommie-lie schrieb:
GPC schrieb:
hab mal im Standard nachgesehen und bin auf folgende Punkte gestossen:
14.5 habe ich doch auch zitiert :p

Joah, aber der war mir zu allgemein, ich fand die Beschreibung mit 14.5.5 und 14.5.5.1 exakter und aufschlussreicher...

Zitat:
GPC schrieb:
Okay?
Du musst mit mir nicht verhandeln, es ist dein Artikel. Ich wollte lediglich Fehler (oder was meiner Meinung nach welche sind) aufzeigen :)

Es sind Fehler, daher werde ich sie auch korrigieren. Jeder will hier schließlich gute Arbeit abliefern, wenn auch verspätet^^
Wir sind sogar auf aufmerksame Leser angewiesen, wenn wir Fehler finden wollen, die die interne Revision überlebt haben. :)

MfG

GPC
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 11:56:34 12.08.2006   Titel:              Zitieren

Okay, Abschnitt umgeschrieben :)
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 12:12:57 12.08.2006   Titel:              Zitieren

GPC schrieb:
Okay, Abschnitt umgeschrieben :)
Besser!

Aber nur um bei den vorhandenen Beispielen zu bleiben und anhand eines Beispiels alles zu erklären könnte man die Überladung von minimum<>() auch für C-Strings zeigen.
Also nach dem Motto:
Zitat:
Eine echte Spezialisierung unseres minimum<>()-Templates für Strings würde demnach also so aussehen:
C/C++ Code:
template<>
inline const char *&minimum(const char *&a, const char *&b) {
  return ((strcmp(a, b) < 0) ? b : a);
};
C/C++ Code:
template<>
inline const char *&minimum(const char *&a, const char *&b) {
return ((strcmp(a, b) < 0) ? b : a);
};
C/C++ Code:
template<>
inline const char *&minimum(const char *&a, const char *&b) {
  return ((strcmp(a, b) < 0) ? b : a);
};
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 12:18:57 12.08.2006   Titel:              Zitieren

Hem, ich dachte ich bring mal was anderes als das ausgelutschte minimum... aber in diesem Kontext verdeutlicht es die Spezialisierung wohl besser. :)
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 13:20:11 12.08.2006   Titel:              Zitieren

Sorry, aber mein Code war auch noch falsch.
Dein Template verlangt "const T &", also eine konstante Referenz. Meine "char *"-Spezialisierung gibt ihm aber eine nicht-konstante Referenz auf einen Pointer auf einen const char. Sowas passiert, wenn man mal eben schnell ohne Compiler ein Template zusammensetzt und noch nicht ganz wach ist...
Das Template mit Inline mit const und allem drum und dran wird für einen Anfänger in dieser Form vielleicht etwas haarig, aber richtig (und mit Erklärung) wäre die Spezialisierung für "char *" folgendermaßen:
Zitat:
C/C++ Code:
template<>
inline const char *const &minimum(const char *const &a, const char *const &b) {
  return ((strcmp(a, b) < 0) ? b : a);
};
C/C++ Code:
template<>
inline const char *const &minimum(const char *const &a, const char *const &b) {
return ((strcmp(a, b) < 0) ? b : a);
};
C/C++ Code:
template<>
inline const char *const &minimum(const char *const &a, const char *const &b) {
  return ((strcmp(a, b) < 0) ? b : a);
};
Man könnte zwar Denken, daß ein "const char *&a" ausreichen sollte, wenn man für T einfach stupide ein "char *" einsetzt, aber so denkt der C++-Standard nicht. Die Typen im generischen Template sind "const T&", was einer konstanten Referenz auf T entspricht. Wollen wir das gleiche mit einem Pointer erreichen, darf nicht das Ziel des Pointers const sein, sondern die Referenz. Somit würde eine Template-Spezialisierung für "char *" folgendermaßen aussehen:
C/C++ Code:
[cpp]template<>
inline char *const &minimum(char *const &a, char *const &b) {
  return ((strcmp(a, b) < 0) ? b : a);
};
C/C++ Code:
[cpp]template<>
inline char *const &minimum(char *const &a, char *const &b) {
return ((strcmp(a, b) < 0) ? b : a);
};
C/C++ Code:
[cpp]template<>
inline char *const &minimum(char *const &a, char *const &b) {
  return ((strcmp(a, b) < 0) ? b : a);
};
Die Typen entsprechen einer konstanten Referenz auf einen Zeiger auf einen char. Natürlich kann man dadurch den String selbst noch verändern, um also auch noch den char konstant zu deklarieren, benötigen wir das zweite const, was insgesamt den Typ "const char *const &" ergibt. Die Spezialisierung ist nun "template<const char *>minimum(), also eine Spezialisierung für "const char *" und nicht mehr nur für "char *".

Ist zwar sehr verwirrend, aber durch "const T &" legst du ja schon relativ verwirrungsträchtig vor ;)

Ist ein wenig ins Unreine geschrieben, die perfekte Integration in deinen Artikel überlasse ich dir (hängt auch davon ab, wie sehr du deinen Artikel über Templates auch die "const correctness" beleuchten lassen willst).
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 19:57:41 12.08.2006   Titel:              Zitieren

Zitat:
Sorry, aber mein Code war auch noch falsch.

Tjo, mir ist's in der Eile auch nicht aufgefallen, das kommt von Copy & Paste^^

Deine Erklärung ist gut, jedoch befürchte ich, dass es an der Stelle etwas zu viel wird, zum Einen das Template und dann noch die const-correctness (die ich übrigens in meinem Pointer-Artikel erläutert habe :) ). Daher bin ich wieder auf das einfache Beispiel umgestiegen.

Du bist ziemlich fit, was C++ angeht, besonders Templates... darf ich dich hierauf aufmerksam machen? Na wie sieht's aus, hättest du Zeit und Lust für den Artikel?

MfG

GPC
tommie-lie
Mitglied

Benutzerprofil
Anmeldungsdatum: 01.01.2006
Beiträge: 74
Beitrag tommie-lie Mitglied 20:34:18 12.08.2006   Titel:              Zitieren

GPC schrieb:
jedoch befürchte ich, dass es an der Stelle etwas zu viel wird
Ja, ich auch.

GPC schrieb:
zum Einen das Template und dann noch die const-correctness (die ich übrigens in meinem Pointer-Artikel erläutert habe :) ).
Wenn die Geschichte mit Referenzen auf Konstanten in dem Artikel erklärt wird, würde ich für jeden, der wissen will, was "const char *const &" bedeutet, dorthin verlinken. Früher oder später wird man ohnehin const-correctness kapieren müssen ;)

GPC schrieb:
Du bist ziemlich fit, was C++ angeht, besonders Templates...
Geht so. Ich scheine nur der einzige zu sein, der bei Diskussionen "C++ gegen den Rest der Welt" immer der Meinung ist, C++ habe eine einfach zu verstehende Syntax. :D

GPC schrieb:
darf ich dich hierauf aufmerksam machen? Na wie sieht's aus, hättest du Zeit und Lust für den Artikel?
Habe ich so noch nicht mit gearbeitet, aber die Beispiele im Wikipedia-Artikel haben was... (sind allerdings in dieser Formatierung ohne Highlighting ziemlich unleserlich ;)). Außerdem habe ich nicht vor, mir auch noch großartig Bücher zu kaufen. Und ich müsste mir erstmal einen Algorithmus aus den Fingern saugen, der vom Verständnis her einfach ist und der sich auch syntaktisch nicht allzu verwirrend in C++-Templates gießen lässt. Wie gesagt, schon für das Potenzierungs-Beispiel von Wikipedia brauchte ich einen Syntaxhighlighter und zweimaliges Lesen (die Fakultät im englischen Aritkel war da deutlich einfacher).
Vielleicht kriege ich ja passende Beispiele serviert, wenn ich studiere.
Aber ich biete mich gerne zum Gegenlesen an, wenn einer einen Artikel hat.
Blubbbbbbbbbbbbbbbbbbbbby
Unregistrierter




Beitrag Blubbbbbbbbbbbbbbbbbbbbby Unregistrierter 16:16:32 28.04.2007   Titel:              Zitieren

Hi,

hab das gerade mal nen bisschen ausprobiert, aber folgendes verstehe ich nicht:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef MY_CLASS_H
#define
MY_CLASS_H

#include
<iostream>

template <typename T>
class MyClass
{
    private:
        T m_Element;

    public:
        MyClass( void ) { ; }
        ~MyClass( void ) { ; }
       
        void setElement( T NewElement )
        {
            std::cout << "[Typ]NewElement: ???" << std::endl;
            m_Element = NewElement;
        }

        //template< > // Wie funktioniert das nun!?
        void setElement( int NewElement )
        {
            std::cout << "[Typ]NewElement: int" << std::endl;
            m_Element = NewElement;
        }

        T getElement( void ) { return m_Element; }

        void PrintElement( void )
        {
            std::cout << "Element: " << m_Element << std::endl;
        }
};

#endif
/* MY_CLASS_H */
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef MY_CLASS_H
#define
MY_CLASS_H

#include
<iostream>

template <typename T>
class MyClass
{
private:
T m_Element;

public:
MyClass( void ) { ; }
~MyClass( void ) { ; }

void setElement( T NewElement )
{
std::cout << "[Typ]NewElement: ???" << std::endl;
m_Element = NewElement;
}

//template< > // Wie funktioniert das nun!?
void setElement( int NewElement )
{
std::cout << "[Typ]NewElement: int" << std::endl;
m_Element = NewElement;
}

T getElement( void ) { return m_Element; }

void PrintElement( void )
{
std::cout << "Element: " << m_Element << std::endl;
}
};

#endif
/* MY_CLASS_H */
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#ifndef MY_CLASS_H
#define
MY_CLASS_H

#include
<iostream>

template <typename T>
class MyClass
{
    private:
        T m_Element;

    public:
        MyClass( void ) { ; }
        ~MyClass( void ) { ; }
       
        void setElement( T NewElement )
        {
            std::cout << "[Typ]NewElement: ???" << std::endl;
            m_Element = NewElement;
        }

        //template< > // Wie funktioniert das nun!?
        void setElement( int NewElement )
        {
            std::cout << "[Typ]NewElement: int" << std::endl;
            m_Element = NewElement;
        }

        T getElement( void ) { return m_Element; }

        void PrintElement( void )
        {
            std::cout << "Element: " << m_Element << std::endl;
        }
};

#endif
/* MY_CLASS_H */


Bei mir compiliert der Code, aber so solls ja glaube nicht gemacht werden, ohne das template < >?

Danke

Gruß
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 23:50:24 28.04.2007   Titel:              Zitieren

Was willst du überhaupt machen? Die Methode setElement für int spezialisieren?
Blubbby
Unregistrierter




Beitrag Blubbby Unregistrierter 23:58:10 28.04.2007   Titel:              Zitieren

Richtig. Genau das. Ich weiß, dass das schwachsinnig ist, aber irgendwie muss ich das ja ausprobieren nur klappt das nicht...

/edit by GPC: nick gekürzt


Zuletzt bearbeitet von GPC am 00:58:34 29.04.2007, insgesamt 1-mal bearbeitet
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 01:18:20 29.04.2007   Titel:              Zitieren

Na ja, mir fehlt etwas der konkrete Anwendungsfall, aber wenn du z.B. den Code ausführst:
C/C++ Code:
//hier dein Code

int main() {
  MyClass<float> fc;
  fc.setElement(2.5f);
  fc.setElement(2);
}
C/C++ Code:
//hier dein Code

int main() {
MyClass<float> fc;
fc.setElement(2.5f);
fc.setElement(2);
}
C/C++ Code:
//hier dein Code

int main() {
  MyClass<float> fc;
  fc.setElement(2.5f);
  fc.setElement(2);
}


dann gibt's folgende Ausgabe:
Code:
gpc@desktop:~$ g++ -o main main.cpp
gpc@desktop:~$ ./main
[Typ]NewElement: ???
[Typ]NewElement: int
Code:
gpc@desktop:~$ g++ -o main main.cpp
gpc@desktop:~$ ./main
[Typ]NewElement: ???
[Typ]NewElement: int
Code:
gpc@desktop:~$ g++ -o main main.cpp
gpc@desktop:~$ ./main
[Typ]NewElement: ???
[Typ]NewElement: int


Btw. Bitte verwende einen kürzeren Nick. Danke :)

MfG

GPC
Blubbby
Unregistrierter




Beitrag Blubbby Unregistrierter 14:45:19 29.04.2007   Titel:              Zitieren

So habe ich aber keine Spezialisierung sondern nur eine normale Überladung. Kann doch nicht richtig sein?
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 18:17:51 29.04.2007   Titel:              Zitieren

Stimmt, das ist Überladung. Da du aber ein Klassentemplate hast, kannst du auch nur das spezialisieren (partiell oder vollständig). D.h. entweder die Überladung oder du machst es so:

C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Generisch:
template <typename T>
struct Foo {
  T bar;

  void set_bar(const T &x) { bar = x; }
  const T& get_bar() const { return bar; }
};

//Für ints:
template <>
struct Foo<int> {
  //hier spezialisierte Versionen der Methoden von oben
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Generisch:
template <typename T>
struct Foo {
T bar;

void set_bar(const T &x) { bar = x; }
const T& get_bar() const { return bar; }
};

//Für ints:
template <>
struct Foo<int> {
//hier spezialisierte Versionen der Methoden von oben
};
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Generisch:
template <typename T>
struct Foo {
  T bar;

  void set_bar(const T &x) { bar = x; }
  const T& get_bar() const { return bar; }
};

//Für ints:
template <>
struct Foo<int> {
  //hier spezialisierte Versionen der Methoden von oben
};

Damit hast du allerdings die Klasse spezialisiert.


Template-Methoden spezialisieren geht auch, aber dann brauchst du template-Methoden.
camper
Mitglied

Benutzerprofil
Anmeldungsdatum: 06.08.2004
Beiträge: 5052
Beitrag camper Mitglied 21:51:00 30.04.2007   Titel:              Zitieren

btw, SFINAE steht für substitution failure is not an error
Badestrand
Mitglied

Benutzerprofil
Anmeldungsdatum: 29.08.2006
Beiträge: 4342
Beitrag Badestrand Mitglied 18:58:32 07.09.2008   Titel:              Zitieren

Magst du evtl noch die Syntax für Template-friend-Deklarationen mit reinnehmen? Solche speziellen Sachen vergess ich immer, brauch's ja auch fast nie :) edit: Hier meine ich template<typename[,...]> friend class XYZ;
Achso, und magst du vielleicht auch noch mal explizit reinschreiben, dass es keine partiell spezialisierten Funktionen gibt? Hatte neulich vergessen, dass es nur mit Klassen geht und mir 'nen Wolf gesucht :D


Zuletzt bearbeitet von Badestrand am 19:19:19 07.09.2008, insgesamt 1-mal bearbeitet
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6290
Beitrag GPC Moderator 22:14:18 07.09.2008   Titel:              Zitieren

Kann ich machen, aber nach der Klausurphase...
C/C++ Forum :: Die Artikel ::  Einführung in die Programmierung mit Templates   Auf Beitrag antworten

Zeige alle Beiträge auf einer Seite




Nächstes Thema anzeigen
Vorheriges Thema anzeigen
Sie können keine Beiträge in dieses Forum schreiben.
Sie können auf Beiträge in diesem Forum antworten.
Sie können Ihre Beiträge in diesem Forum nicht bearbeiten.
Sie können Ihre Beiträge in diesem Forum nicht löschen.
Sie können an Umfragen in diesem Forum nicht mitmachen.

Powered by phpBB © 2001, 2002 phpBB Group :: FI Theme

c++.de ist Teilnehmer des Partnerprogramms von Amazon Europe S.à.r.l. und Partner des Werbeprogramms, das zur Bereitstellung eines Mediums für Websites konzipiert wurde, mittels dessen durch die Platzierung von Werbeanzeigen und Links zu amazon.de Werbekostenerstattung verdient werden kann.

Die Vervielfältigung der auf den Seiten www.c-plusplus.de, www.c-plusplus.info, www.c-sar.de, www.c-plusplus.net und www.baeckmann.de enthaltenen Informationen ohne eine schriftliche Genehmigung des Seitenbetreibers ist untersagt (vgl. §4 Urheberrechtsgesetz). Die Nutzung und Änderung der vorgestellten Strukturen und Verfahren in privaten und kommerziellen Softwareanwendungen ist ausdrücklich erlaubt, soweit keine Rechte Dritter verletzt werden. Der Seitenbetreiber übernimmt keine Gewähr für die Funktion einzelner Beiträge oder Programmfragmente, insbesondere übernimmt er keine Haftung für eventuelle aus dem Gebrauch entstehenden Folgeschäden.