Wo landen Templates?

Enumerator

Mitglied Kamel
Hi!

Mal wieder eine Grundsatzfrage: wenn ich (in einer Header-Datei die zu einer Programmbibliothek gehört) Template-Klassen definiere, diese noch in der selben Header-Datei mittels typedef o.Ä. an bestimmte Typen binde -- ist dann der Anwender der Bibliothek in der Lage, auf die Symbole zuzugreifen oder erstellt er fleißig seine eigenen?

Beispiel, test.hpp:
Code:
// ...
namespace My
{
    template<typename TYPE>
    class Test
    {
    public:
        // ...
        virtual operator TYPE(void) const throw();
        virtual const char *getSomething(void) const throw();
        // ...
    protected:
        TYPE value;
    };

    typedef Test<char> Char;

    class NegativeInt
        : public Test<int>
    {
    public:
        virtual operator int(void) const throw();
    };
}

// ...

template<typename TYPE>
My::Test<TYPE>::operator Type(void) const throw()
{
    return value;
}

template<typename TYPE> const char*
My::Test<TYPE>::getSomething(void) const throw()
{
    return "Something...";
}

template<> const char*
My::Test<char>::getSomething(void) const throw()
{
    return "Something else...";
}

// ...
Beispiel, test.cpp:
Code:
// ...

template<> const char*
My::Test<int>::getSomething(void) const throw()
{
    return "Something else...";
}

My::NegativeInt::operator int(void) const throw()
{
    return 0 - value;
}

// ...
Das ganze kompiliert und gelinkt; sagen wir in "libmytest" (so, dll, wasweisich)...
Wen jetzt ein anderer daherkommt, test.hpp includiert und mit -lmytest verlinkt:
was passiert?
Code:
#include <iostream>
#include "my/test.hpp"

int
main(void)
{
    My::NegativeInt n(5);
    My::Test<void*> p(NULL);
    My::Char c('A');

    std::cout << c.getSomething() << std::endl; // "Something else..."
    std::cout << p.getSomething() << std::endl; // "Something..."
    std::cout << n.getSomething() << std::endl; // "Something else..." oder "Something..."?

    return 0;
}
Ich wüsste nicht nur gern, was passiert, sondern vor allem warum... :-(
Und gibt es Unterschiede - je nachdem welchen Compiler, Linker oder welches OS man benutzt?

Gruß
Enum
 
Hi.

Templates sind erstmal nichts als Schall und Rauch. Jedenfalls bevor sie instanziiert werden.

Zuersteinmal zum typedef. Ein typedef definiert lediglich einen Alias. Das war's. Eine Instanziierung des Templates findet deshalb nicht statt.

Bei der Instanziierung einer Template-Klasse werden nur die benötigten Member-Funktionen instanziiert die auch tatsächlich verwendet werden, die anderen nicht.

Nun sprichst du die explizite Spezialisierung von Template Membern an.

Du spezialisierst die Test::getSomething Methode für den Typ char und int. Durch diese Spezialisierung bei der kein Template-Parameter angegeben ist, wird der Code für diese Methoden tatsächlich auch generiert.

Wenn du also deinen Code kompilierst und dabei eine Bibliothek erstellst, sind dort die Definitionen für NegativeInt::operator int() const, Test<int>::getSomething() und Test<char>::getSomething() enthalten.

Beim Kompilieren einer anderen Übersetzungseinheit sind allerdings die expliziten Spezialisierungen der Template Member nicht verfügbar.

Außerdem führt der Code in der main() Funktion nun endlich dazu das das Klassen-Template instanziiert wird.

Dabei werden nun wieder Instanzen der einzelnen Member des Klassen-Templates erstellt, also für Test<void*>::getSomething() und Test<char>::getSomething, aber nicht für Test<T>::operator T() const.

Diese Instanziierungen werden natürlich von den verfügbaren Templatedefinitionen erstellt.

Letztendlich liegt es dann am Linker, der nun versucht redundante Template-Instanziierungen zu entfernen.

Da ist (vom C++ Standard) nicht definiert wie das geschieht. Einige Compiler unterstützen LTO (link time optimzation) bzw. whole program optimization. Dafür müssen aber Informationen über den ursprünglichen (Template-)Code in die Bibliothek eingebettet sein.

Einige Compiler unterstützen auch die extern template Definition.

So ist es (meiner Meinung nach) mehr oder weniger Zufall was dort in der dritten Zeile ausgegeben wird, der Linker wird vermutlich nach dem Grundsatz "wer zuerst kommt, mahlt zuerst" verfahren und die Instanziierung aus der Bibliothek verwerfen, so dass dann (vermutlich?) "Something..." ausgegeben wird.

Um das Problem zu vermeiden könntest du die expliziten Spezialisierungen in der Headerdatei vorher deklarieren, so das vermieden wird das ein Compiler seine eigene Instanziierung der primären Template-Definition vornimmt.

Gruß
 
Hi!

Erstmal vielen Dank für die umfangreiche Antwort.
Nun stellt sich mir noch die Frage, wie ich z.B. mit GNU Autotools auf die Unterstützung von extern template testen kann.
Es gibt zwar im Autoconf Archiv auf nongnu.org ein entsprechendes Makro, allerdings ist das als überholt gekennzeichnet...

Gruß
Enum
 
Hi!

Erstmal vielen Dank für die umfangreiche Antwort.
Nun stellt sich mir noch die Frage, wie ich z.B. mit GNU Autotools auf die Unterstützung von extern template testen kann.
Es gibt zwar im Autoconf Archiv auf nongnu.org ein entsprechendes Makro, allerdings ist das als überholt gekennzeichnet...
... weil es in AX_CXX_EXTERN_TEMPLATE umbenannt wurde?

Übrigens, wenn es dir nur um die explizit spezialisierten Template Member Funktionen geht, dann brauchst du keine extern template Unterstützung. Wie gesagt, du mußt nur eine Vorwärtsdeklaration der Spezialisierungen angeben.

Gruß
 
Hi!
... weil es in AX_CXX_EXTERN_TEMPLATE umbenannt wurde?
Ok. ;)

Übrigens, wenn es dir nur um die explizit spezialisierten Template Member Funktionen geht, dann brauchst du keine extern template Unterstützung.
Ja, ich würde nur gern vermeiden dass z.B. abgeleitete Bibliotheken und Programme, die sowol diese als auch die "originale" Bibliothek verwenden, alle ihre eigenen Instanzen kreieren...

Gruß
Enum
 
Zurück