Template Funktion in normaler Klasse nutzen?

Sadakazu

Well-Known Member
Moin moin...
Ich habe mir eine "utils" Klasse gemacht, wo andere klassen drauf zugreifen sollen, wo bestimmte Funktionen drin sind, die über das gesamte Programm hier und da mal von untershiedlichen Klassen benutzt werden...

So jetzt habe ich da eine Template funktion din die ein string Array entgegen nehmen soll...

Code:
class utils {
/* Schnipp */
template<size_t N>
void doSomth(const std::string (&lines)[N]);
}
Soweit so gut... rufe ich doSomth innerhalb der utils auf kann ich die Funktion auch korrekt ansprechen (mit unterschiedlich großen string arrays)...

Aber wenn ich jetzt über eine andere klasse oder über die main.cpp diese Funktion aufrufen will, gibts beim Kompilieren einen fehler...
Zunächst mein aufruf:
Code:
/* ne menge code vorweg..... bis irgendwann */
std::string myLines[] {
    "test1", "test2", "test3", "test4"
}
utils u; // das objekt existiert bereits früher, dient nur zur veranschaulichung was u. ist
u.doSomth(myLines);

Als Compile fehler kommt die meldung:
Code:
main.cpp:(.text+0x102): Nicht definierter Verweis auf `void utils::doSomth()<2ul>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const (&) [2ul])'
collect2: error: ld returned 1 exit status

Lösche ich das aus der main.cpp und rufe doSomth() innerhalb der utils klasse auf:
Code:
void doMyDoSomth() {
     std::string myLines[] {
          "test1", "test2", "test3", "test4"
     }
     doSomth(myLines);
}

Dann wird komplett durch kompiliert und die funktion macht das was sie soll.....

Warum? :D
 
Ich weiß a.d. Kopf den richtigen Syntax nicht, aber du must natürlich den template Parameter N mit angeben. Etwa so:
Code:
utils.doSomth<4>(myLines)
Tempkates funktionieren über name-mangling, d.h. template Parameter werden im Namen codiert. Denke dran dass bei jedem neuen N eine neue Instanz entsteht..
 
ne hat auch nichts gebracht... aber scheinbar erkennt er ja die größe des Arrays richtig... sagt aber er kann keinen verweis auf die funktion utils::doSomth() finden (undefined reference)
Sagt mir eigentlich das er nicht drauf zugreifen kann.... aber die funktion liegt im public....
 
Der Compiler erkennt die Funktion wg..des Name-Mangling nicht oder weil die Funktion nirgends instantiiert ist. Es gibt verschiedene Möglichkeiten der Instantierung dazu müßte man sich das genauer ansehen. Ich habe aber z.Z. keinen C++ Compiler am laufen so dass ich dir da. nicht weiter helfen kann.
 
Wie hast du den Quellcode aufgeteilt? Wenn du templates verwendest muss der gesamte Code bei der Instanziierung sichtbar sein. Kurz gefasst: Template-Code muss im Header stehen, Vorwärtsdeklarationen funktionieren nur eingeschränkt.
 
lol na toll... jetzt wollte ich grad zeigen was ich meine... hab alles ein bisschen eindeutiger benannt und sowohl die main.cpp als auch die utils klasse nur kurz angepasst...

jetzt funktioniert es....
Hier trotzdem mal der Code:
main.cpp
Code:
#include <iostream>
#include <string>
#include "utils/utils.hpp"


template<size_t N>
void printArray(const std::string &para1, const std::string (&array)[N]) {
    std::cout << para1 << std::endl;
    for(const auto &it : array) {
        std::cout << it << " ";
    }
    std::cout << std::endl;
}

int main(int argc, char *argv[]) {

    //init utils
    utils u;

    // init para list for printArray
    std::string para1 = "this is para 1";
    std::string para2[] = {"this",
                "is",
                "para",
                "2",
                "as",
                "an",
                "string",
                "array"
    };

    /* local printArray call*/
    printArray(para1, para2);

    /* utils::classPrintArrayCall*/
    u.classPrintArrayCall();

    /* utils::printArray direct*/
    u.printArray(para1, para2);

    return 0;
}

utils.hpp
Code:
#ifndef UTILS_HPP
#define UTILS_HPP
#include <iostream>
#include <string>

class utils {
public:
    utils();
    utils(const utils& orig);
    virtual ~utils();

    void classPrintArrayCall();

    template<size_t N>
    void printArray(const std::string &para1, const std::string (&array)[N]);
private:

};

#endif /* UTILS_HPP */

utils.cpp
Code:
#include "utils.hpp"

utils::utils() {
}

utils::utils(const utils& orig) {
}

utils::~utils() {
}
void utils::classPrintArrayCall() {
    /*This Function calls the utils::printArray()*/
    // init para list for printArray
    std::string para1 = "this is para 1";
    std::string para2[] = {"this",
                "is",
                "para",
                "2",
                "as",
                "an",
                "string",
                "array"
    };

    printArray(para1, para2);
}
template<size_t N>
void utils::printArray(const std::string &para1, const std::string (&array)[N]) {
    std::cout << para1 << std::endl;
    for(const auto &it : array) {
        std::cout << it << " ";
    }
    std::cout << std::endl;
}
So, was hab ich jetzt effektiv geändert?
1.) das template in die main.cpp mit kopiert um es direkt zu verwenden....
2.) in der klasse utils die funktion utils::classPrintArrayCall() hinzugefügt
3.) die templates von writeFile() in printArray() umbenannt
4.) in der main.cpp alle möglichkeiten zum aufruf von printArray eingebaut....

Mehr habe ich da nicht verändert....
Warum funktioniert es mit dem namen printArray() aber mit dem namen writeFile nicht?
Schlussendlich soll das template später den pfad zu einer datei, sowie ein string Arrray mit config inhalten übergeben bekommen, und die daten in einer Datei speichern.


PS: So funktionierts übrigens auch wenn ich aus der main.cpp das template und den funktionsaufruf auskommentiere.... sprich... so wie es sein soll....

[EDIT]
So... da ist er wieder.....
main.cpp
Code:
#include <iostream>
#include <string>
#include "utils/utils.hpp"


template<size_t N>
void printArray(const std::string &para1, const std::string (&array)[N]) {
    std::cout << para1 << std::endl;
    for(const auto &it : array) {
        std::cout << it << " ";
    }
    std::cout << std::endl;
}

int main(int argc, char *argv[]) {
  
    //init utils
    utils u;
  
    // init para list for printArray
    std::string para1 = "this is para 1";
    std::string para2[] = {"this",
                "is",
                "para",
                "2",
                "as",
                "an",
                "string",
                "array"
    };

    /* local printArray call*/
    printArray(para1, para2);
  
    /* utils::printArray direct*/
    u.writeFile(para1, para2);
  
    return 0;
}

utils.hpp
Code:
#ifndef UTILS_HPP
#define UTILS_HPP
#include <iostream>
#include <string>

class utils {
public:
    utils();
    utils(const utils& orig);
    virtual ~utils();
  
    void printWellcome();
    void printByeBye();
  
    template<size_t N>
    void writeFile(const std::string &path, const std::string (&array)[N]);
  

private:
    std::string version_ = "0.0.1";
    std::string projectname_ = "Test-prog";

};

#endif /* UTILS_HPP */

utils.cpp
Code:
#include "utils.hpp"

utils::utils() {
}

utils::utils(const utils& orig) {
}

utils::~utils() {
}


void utils::printWellcome() {
    std::cout << "Wellcome do " << this->projectname_ << " Version " << this->version_ << std::endl;
}
void utils::printByeBye() {
  
}

template<size_t N>
void utils::writeFile(const std::string &path, const std::string (&array)[N]) {
    std::cout << path << std::endl;
    for(const auto &it : array) {
        std::cout << it << " ";
    }
    std::cout << std::endl;
}

Compiler Error:
Code:
cd '/usr/devel/testrog2'
/usr/local/bin/gmake -f Makefile
clang++ -std=c++11 -c -Wall utils/utils.hpp utils/utils.cpp
clang++ -std=c++11 -o testprog2 main.o utils.o
main.o: In function `main':
main.cpp:(.text+0x2682): undefined reference to `void utils::writeFile<8ul>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const (&) [8ul])'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
gmake: *** [Makefile:7: all] Fehler 1

Was wurde effektiv geändert?
classPrintArrayCall() wurde gelöst
printWelcome und printByeBye wurden hinzugefügt
printArray() wurde in writeFile umbenannt....
 
Zuletzt bearbeitet:
Die Implementierung von `utils::writeFile()` darf nicht in `utils.cpp` sein, wenn sie in `main.cpp` verwendet wird, da der Compiler sie zum instanzieren der Templates braucht. Verschiebe die Implementierung in den Header.
 
AAAHHHSOOOOO :D ja alles klar....

[EDIT]
Eh... nö... hab die implementation aus der cpp gelöscht, und in den Header kopiert...
Nope... der Error bleibt wie oben gepostet....

[NOCHMAL EDIT]
Eh okay?
nutze NetBeans als IDE.... sag ich dort.. build... schmeißt er fehler aus...
geh ich auf ne shell und sage make.... baut er durch....
Logik?
 
Zuletzt bearbeitet:
Öh, ich habe den Code leider nur überflogen, bei näherer Betrachtung erscheint mir der Typ des `array`-Arguments komisch.
Ich kenne die Syntax von C99, was die Bedeutung in C++ angeht bin ich aber überfragt.
Da du eine for-each Schleife verwendest scheinst du ein `std::array` zu suchen?
Daher hier ein genereller Vorschlag: benutze gleich Iteratoren:

Code:
template<typename Iterator, size_t N>
void utils::writeFile(const std::string &path, Iterator begin, Iterator end) {
    std::cout << path << std::endl;
    // entweder
    std::for_each(begin, end, [](Iterator iter) { std::cout << *iter << std::endl; });
    // oder
    for (const auto &it = begin; it != end; ++it) {
        std::cout << *iter << std::endl;
    }
    std::cout << std::endl;
}

Das wäre eine ziemlich flexible Lösung, die auch mit mehr Containern als nur Arrays funktioniert.
 
najaaa was ich eigentlich vor habe ist folgendes...
ein String Array an eine klassen Funktion übergeben. diese Funktion soll dann über einen istream erst die Datei einlesen die als &path übergeben wurde... mit den werten meines string arrays vergleichen, und ggf über den ostream Einträge nachtragen, falls die Datei gar nicht existiert, dann eben eine neue Datei anlegen.

Das ganze soll dann soweit "flexibel" sein, das ich die unterschiedlichsten config dateien einlesen kann, verlgiechen kann und ggf anpassen respektive neu anlegen kann...
Ein beispiel dafür:
/usr/local/etc/X11/xorg.conf.d/20-keyboard.conf
Git es diese datei?
Wenn ja? gucke rein, vergleich die werte mit meinem Array
Fehlt was oder ist was "falsch" konfiguriert, bessere den "fehler" aus..
Wenn nein erstelle eine neue datei und schreibe mein String Array in die datei hinein.
Speichere und schließe die Datei....
Natürlich nicht alles in einer einzigen funktion, aber für diese funktionen benötige ich eben die Templates da die datei keyboard.conf angenommen 8 zeilen lang sein kann, während die driver.conf nur 4 zeilen lang ist...
Jede zeile symbolisiert ein element des Arrays....
 
So wie das klingt würdest du mit den Template-Funktionen deine Binärgröße aufblähen, denn für jede neue Instanz wird der gesamte Code dupliziert. Wenn du das mit Iteratoren machst, dann wird die Funktion nur einmal gebraucht (vorausgesetzt du verwendest du eine Art von Iteratoren).

Aber deine Schilderung klingt als würdest du eher nach einem key-value-store wie einer `std::map` suchen.

Und falls du auf deinem bisherigen Weg weiter machen möchstest: lies dir die Dokumentation zu `std::array` durch.
 
Aber deine Schilderung klingt als würdest du eher nach einem key-value-store wie einer `std::map` suchen.
Eigentlich nicht... dann ehr einen vector<std::string>
Oder fragen wir mal anders....
Was wäre denn die bessere Vorgehensweise, um den Inhalt für eine Konfigurationsdatei, an eine Funktion zu übergeben, die den "Inhalt" in eine Datei schreibt...

Das Problem ist halt, das auch Konfigurationen dabei sein können, die 20 30 Zeilen und länger sein können.
und um den Vector erstmal aufzubauen wäre es sehr sinnfrei 30x vector.push_back("VALUE") einzutackern...
Deswegen war meine Idee einen normalen String zu nehmen.... den als ein Array zu deklarieren und dann das gesamte Array an die entsprechenden Funktionen zu leiten...

Kleines Sorry an dieser stelle... ich bin noch recht frisch was C++ angeht... bin jetzt seit ... knapp einem halben Jahr dabei (immer mal wieder) und hab daher noch nicht alle kniffe und tricks raus ;) von der Erfahrung mal ganz abgesehen...

Eine Voraussetzung hab ich... es darf ausschließlich die Standard Bibliothek benutzt werden... also kein boost oder andere Biblios...

Und falls du auf deinem bisherigen Weg weiter machen möchstest: lies dir die Dokumentation zu `std::array` durch.
da hätte ich aber wieder das Problem das ich das Array einem festen wert von vorneherein geben muss, was zu Fehlern führen kann wenn ich mal bei einem array die länge ändern will..

Ich muss bei dem Array genau wissen ob mein Array 2 Elemente fassen soll oder 20... bei der Schreibweise mit:
Code:
 std::string myString[] = { "Value1", "Value2", "Value3" }
umgehe ich ja das Problem ;)
Außerdem ist diese zeile deutlich angenehmer zu lesen und vorallem auch angenehmer zu schreiben als dies hier:
Code:
std::vector<std::string> vector;
vector.push_back("Value1");
vector.push_back("Value2");
vector.push_back("Value3");

für den iterator nachher der über den Vector/String Array drüber jagt sollte das eigentlich egal sein ob er ein Vector bekommt oder ein String Array....
zumal ich ja die schreibweise:
Code:
for(const auto &it : parameter) {
    // Do stuff withi it
}
recht Angenehm finde.... ;)
 
Zurück
Oben