Frage zur Überladung von Konstruktor mit Iterator-Parameter



  • Hallo,
    Ich habe eine Klasse, deren Elemente mit Hilfe zweiwer Iteratoren initialisiert werden sollen. Dabei könnte ich die Initialisierung deutlich performanter gestalten, wenn RandomAccess Iteratoren zur Verfügung stünden. Ich habe also zwei Konstruktoren geschrieben, die Iteratoren annehmen

    class foo
    {
        //performante Variante der Initialisierung
        template <class RandomAccessIterator> foo (RandomAccessIterator a, RandomAccessIterator b) {/*...*/}
    
        //weniger performante Variante
        template <class InputIterator> foo (InputIterator a, InputIterator b) {/*...*/}
    }
    

    Der erste Konstruktor stellt, wie bereits erwähnt, aufgrund der "pointer arithmetik", die schnelle Methode der Initialisierung dar. Den zweiten Konstruktor habe ich eingefügt, damit die Klasse auch kompatibel zu Containern der STL ist, die keine RandomAccessIteratoren unterstützen.
    Allerdings bekomme ich die Fehlermeldung, dass der Konstruktor schon vorhanden wäre und der zweite Konstruktor eine Neudeklaration wäre.
    Gibt es eine Möglichkeit, trotzdem beide Varianten zu verwenden? Oder muss ich mich auf InputIteratoren beschränken, damit meine Klasse kompatibel zu allen Containertypen ist?



  • Iterator~ schrieb:

    class foo
    {
        //performante Variante der Initialisierung
        template <class RandomAccessIterator> foo (RandomAccessIterator a, RandomAccessIterator b) {/*...*/}
    
        //weniger performante Variante
        template <class InputIterator> foo (InputIterator a, InputIterator b) {/*...*/}
    }
    

    ...
    Allerdings bekomme ich die Fehlermeldung, dass der Konstruktor schon vorhanden wäre und der zweite Konstruktor eine Neudeklaration wäre.

    Das ist korrekt. Für den Compiler ist "RandomAccessIterator" nur ein Name ohne Bedeutung. Du hast also zwei Templates, die die gleiche Deklaration besitzen.

    In der STL löst man das mit "Tag Dispatching". Den folgende Code habe ich nicht getestet. Aber so müsste es ungefähr gehen:

    #include <iterator>
    
    class foo
    {
    public:
        template <class Iterator>
        foo (Iterator a, Iterator b)
        {
          typedef std::iterator_traits<Iterator> itraits;
          typedef typename itraits::iterator_category icat;
          my_init(a,b,icat());
        }
    
    private:
        template <class terator>
        void my_init (Iterator a, Iterator b, std::input_iterator_tag);
        template <class terator>
        void my_init (Iterator a, Iterator b, std::random_access_iterator_tag);
    }
    

    "icat" ist ein Typ, der die Iterator Kategorie repräsentiert. Diese Typen sind (wahrscheinlich) leere Structs. Sie besitzen aber eine Vererbungsbeziehung. Ein bidirectional_iterator_tag ist zB konvertierbar in ein input_iterator_tag, aber nicht in ein random_access_iterator_tag. Wenn Du foo mit einem "random access"-Iterator nutzt kommen also beide Funktionen "my_init" in Frage. Aber die Auflösug wird dann durch "partial ordering" erledigt. random_access_iterator_tag erbt von input_iterator_tag, ist also "spezieller", was overload resolution angeht.

    "SFINAE" ist neben "Tag Dispatching" noch ein weiterer, nennenswerter Trick aus der Trickkiste. Es lohnt sich, das mal nachzugucken. Willkommen in der herrlich komplizierten Welt der generischen C++-Programmierung. In der nächsten C++-Version soll das alles viel einfacher werden. Mal gucken, ob's klappt. 🙂

    Die Erklärung war jetzt wahrscheinlich ein bissel zu kurz und knapp. Aber Du hast jetzt zumindest nen Anhaltspunkt und kannst Dich diesbezüglich schlau machen.

    Gruß,
    SP



  • Der Code von Sebastian Pizer funktioniert einwandfrei. Allerdings nur für Konstruktoren. Ich habe nun versucht, eine Memberfunktion auf die selbe weise zu implementieren:

    class foo
    {
       public:
        template <class Iterator> void do_something(Iterator* a, Iterator* b)
        {
            do_something_tmp ( a, b, typename std::iterator_traits<Iterator>::iterator_category() );
        }
    
       private:
        template <typename Iterator>
        void do_something_tmp (Iterator a, Iterator b, std::input_iterator_tag)
        {
           //code
        }
    
        template <typename Iterator>
        void do_something_tmp (Iterator a, Iterator b, std::random_access_iterator_tag)
        {
            //code
        }
    
    };
    

    Compilieren tut der code ohne Fehler. Nur wenn ich Iteratoren von zB. vector übergeben will, bekomme ich eine Fehlermeldung, dass es keinen "matching function call" gäbe. Woran liegt das 😕



  • Lass mal die Sterne weg, hm?


Anmelden zum Antworten