Template Template Parameter



  • Tag zusammen,

    folgender Code soll funktionieren:

    #include <list>
    #include <algorithm>
    
    template<class Vertex, class Edge>
    class Graph
    {
    public:
    	typedef int VertexID;
    };
    
    template<template<typename> class List, typename T>
    bool isInList(List<T> const& l, T x)
    {
    	return std::find(std::begin(l), std::end(l), x) != std::end(l);
    }
    
    template<class Vertex, class Edge>
    void fuuuuu(Graph<Vertex, Edge> const& g)
    {
    	typedef Graph<Vertex, Edge> MyGraph;
    	typedef typename MyGraph::VertexID Node;
    	typedef std::list<Node> List;
    
    	List l;
    	Node id;
    
    	if ( isInList(l, id) )
    		return;
    }
    
    int main()
    {
    	Graph<double, float> g;
    
    	fuuuuu(g);
    
    	return 0;
    }
    

    (IDEOne)

    Da hatte ich schon immer Probleme: ich will einen Container an eine Funktion geben. Der Container soll aber ein Templateparameter sein (im obigen Fall die 'List'), aber der Templateparameter der Liste soll auch ein Parameter der Funktion sein.

    Wie? Und warum?

    Edit:

    template<class List>
    bool isInList(List const& l, typename List::value_type x)
    {
    	return std::find(std::begin(l), std::end(l), x) != std::end(l);
    }
    

    Sowas geht ohne Probleme Oo
    Mh, ist mir allerdings nicht allgemein genug.



  • Hallo,

    list erfordert eben 2 Template Argumente. Value-Type und Allocator.



  • list hat noch ein TMPL-Arg.


  • Mod

    Skym0sh0 schrieb:

    Wie? Und warum?

    Nun, das übliche vorgehen wäre, dass du das gar nicht kapselst. Schließlich wirst du nur inflexibler. Es funktioniert zum Beispiel nicht mehr mit Zeigern und Längenangaben und auch nicht mehr mit Teillisten.

    Mh, ist mir allerdings nicht allgemein genug.

    Wie gesagt, du wirst nur weniger allgemein mit deinem Vorhaben, als das allgemeine find. An sich ist es aber möglich, templates an templates zu übergeben, auch mehrmals verschachtelt. Jedoch funktioniert das dann nur noch mit eben genau dieser Verschachtelungstiefe und die genaue Anzahl der Templateparameter muss bekannt und fest sein.
    Letzteres ist besonders bei den STL-Containern verwirrend und hinderlich, da diese ja noch ein paar mehr Templateparameter haben, als man oftmals so wahrnimmt, zum Beispiel einen Allokator. Dann kannst du an deine Funktion aber zum Beispiel nicht mehr wahlweise einen vector (2 Templateparameter) oder ein set (3 Parameter) übergeben.



  • Nimm eben:

    template <template <typename...> class L>
    

    Aber warum nicht gleich Iteratoren, bzw. direkt std::find benutzen?



  • Das heisst, es geht gar nicht groß besser?

    Meine Intention war eigentlich, dass ich jeden Container an meine Funktion übergeben kann, dazu noch ...

    Ach, ich merke, das ist gar nicht zu Ende gedacht. ich stolper jetzt schon über die std::map, weil nach was suche ich da? Key oder Value, tja...

    Ok, irgendwelche Ideen wie ich das sosnt lösen könnte?

    Was ich will ist eigentlich nur:
    - ich will prüfen, ob in einem Container (list, map, vector...) ein bestimmter Wert drinsteckt
    - bei den Assoziativen Containern reicht eine Suche anch Keys, Values sind nicht nötig, bzw. gar nicht gewünscht



  • Kellerautomat@work schrieb:

    Nimm eben:

    template <template <typename...> class L>
    

    Aber warum nicht gleich Iteratoren, bzw. direkt std::find benutzen?

    Weil ich eigentlich nur ein x.contains(y) nachbilden will, aber für alle Container(weil ich noch nicht festgelegt bin). Und jedesmal

    if ( std::find(std::begin(x), std::end(x), y) != std::end(x) )
       ; // ...
    

    zu schreiben ist absolut nicht lesbar, wie ich finde.
    Jedenfalls für einen Sprachunkundigen



  • Mach es doch so:

    template<typename Container>
    bool contains(Container const& c, typename Container::value_type const& value)
    {
        return std::find(c.begin(), c.end(), value) != c.end();
    }
    
    template<typename Key, typename Value>
    bool contains(std::map<Key, Value> const& m, Key const& value)
    {
        return m.find(value) != m.end();
    }
    

  • Mod

    template<typename IteratorType, typename ValueType> bool range_contains_value(IteratorType begin, IteratorType end, ValueType value)
    {
      return std::find(begin, end, value) != end;
    }
    

    🙂
    Ja, sieht aus wie ein Scherz, aber ich schätze für deine Anforderung

    Skym0sh0 schrieb:

    ist absolut nicht lesbar, wie ich finde.
    Jedenfalls für einen Sprachunkundigen

    passt diese Lösung schon ganz gut. Wobei ich persönlich nicht zu sehr Rücksicht nehmen würde auf Sprachunkundige. find != end ist kein obskurer Code, den nur eingeweihte Gurus lesen können. Das ist eine ganz normale Vorgehensweise, die jeder fortgeschrittene Anfänger schon gesehen haben sollte, oder wenigstens bei ersten Lesen verstehen sollte. Leute mit geringeren Sprachkenntnissen sollen sich nicht wundern, wenn sie nichts verstehen. Die verstehen dann auch keine Templates.


  • Mod

    Ethon schrieb:

    Mach es doch so:

    template<typename Container>
    bool contains(Container const& c, typename Container::value_type const& value)
    {
        return std::find(c.begin(), c.end(), value) != c.end();
    }
    

    Oder auch

    template<typename Container>
    bool contains(Container const& r, decltype(std::begin(r)) const& value)
    {
        return std::find(std::begin(c), std::end(c), value) != std::end(c);
    }
    

    Dann funktioniert das sogar mit Arrays.



  • Im body musst du natürlich auch std::begin/std::end verwenden 🤡
    Warum eigentlich nicht so?

    template <typename C, typename T>
    bool contains(C const& c, T const& val)
    {
        using std::begin;
        using std::end;
    
        return std::find(begin(c), end(c), val) != end(c);
    }
    

  • Mod

    camper schrieb:

    Oder auch

    template<typename Container>
    bool contains(Container const& r, decltype(std::begin(r)) const& value)
    {
        return std::find(std::begin(c), std::end(c), value) != std::end(c);
    }
    

    Dann funktioniert das sogar mit Arrays.

    Was mich die ganze Zeit an all diesen Versuchen stört ist, dass es eben nicht realistisch funktioniert, sowohl mit Arrays, als auch nicht mit Iteratoren. Die contains-Funktion wird schließlich von irgendwoher aufgerufen. Sofern das aber nicht die Hauptebene des Anwendungscodes ist, so liegt der Container nicht direkt vor, sondern ein

    void function(int *foo, int length);
    

    oder C++'iger:

    template <typename iterator> function(Iterator begin, Iterator end);
    

    In beiden Fällen ist das contains in dieser Form nicht mehr anwendbar, obwohl dies unter den häufigeren Anwendungsfällen sein dürfte 👎 .

    Geht ebenfalls nicht:

    // Gucken, ob in zweiter Hälfte:
    find(begin + distance(begin, end) /2, end, value) != end;
    


  • Geht ebenfalls nicht:

    Es geht hier aber explizit um eine kürzere Syntax für Suchen im Container, nicht in einer Subrange. Wieso bringen alle immer negative Aspekte einer Idee auf, bei Fällen für welche sie nicht gedacht ist?

    @camper:
    Ohne jetzt zu testen, meinst du nicht decltype(*std::begin(r)) ?



  • Wieso überhaupt den Wertetyp fixieren? Es wäre noch flexibler, würde man nur fordern, dass das zu vergleichende Objekt eben mit dem Elementtyp des Containers vergleichbar ist, nicht implizit ihn in konvertierbar. Diese Idee haben schließlich auch die STL-Algorithmen, und das contains -Funktionstemplate stellt nur einen Wrapper um std::find dar.

    template<typename Range, typename ValT>
    bool contains( Range&& range, ValT&& value )
    {
        auto end = std::end(std::forward<Range>(range));
        return std::find(std::begin(std::forward<Range>(range)), end, std::forward<ValT>(value)) != end;
    }
    

    (Ungetestet)

    ~Edit: Fehlendes forward hinzugefügt.~


  • Mod

    Sone schrieb:

    @camper:
    Ohne jetzt zu testen, meinst du nicht decltype(*std::begin(r)) ?

    ja klar. ABer wie schon gesagt wurde, gibt es eigentlich keinen besonderen Grund, an dieser Stelle nicht jeden beliebigen Typ zu erlauben.



  • Perfect Forwarding ist hier unangebracht.



  • Kellerautomat schrieb:

    Perfect Forwarding ist hier unangebracht.

    Nicht unangebracht, sogar falsch.

    contains(std::vector<int>{1,2,3}, 3);
    
    // führt zu:
    auto end = std::end(std::move(v));
    auto begin = std::begin(std::move(v)); // bäm
    

    Ausserdem frage ich mich, wie viele Hinweise Sone noch braucht, bis er lernt, dass std::begin kein ADL erlaubt.



  • Zu ADL- begin / end : Das ist mir egal. Aber wenn es sein muss, dann ändere ich das Template so, dass innen std::end und std::begin per using -Deklaration bekanntgemacht werden und dann eine unqualified-id benutzt wird.

    Zu den rvalue-Referenzen: Seid ihr eigentlich von allen guten Geistern verlassen? Sowohl begin als auch end sind für rvalue-Referenzen nicht überladen. Das funktioniert wunderbar, kein gebäms, noch sonst irgendetwas.
    http://ideone.com/a0lac6

    Außer jemand definiert sich ein eigenes begin / end welches rvalue-Referenzen berücksichtigt. Daher:

    Perfect Forwarding ist hier unangebracht.

    Ja, das stimmt.


Log in to reply