Die Konstruktoren des std::vector und vector(4, 100)



  • Hi!

    Weil es mir gerade auffällt, hier mal eine kleine Frage.
    Wieso wird bei einem minimalen Beispiel wie hier:

    http://www.cplusplus.com/reference/vector/vector/vector/ schrieb:

    std::vector<int> second (4,100);                       // four ints with value 100
    

    nicht

    template <class InputIterator>
      vector (InputIterator first, InputIterator last,
              const allocator_type& alloc = allocator_type());
    

    statt

    vector (size_type n, const value_type& val,
                     const allocator_type& alloc = allocator_type());
    

    aufgerufen?

    Passt der nicht besser als der andere?
    Weil doch beide Argumenttypen gleich sind, aber die Argumenttypen nicht perfekt zum anderen Ctor passen: size_type aka size_t aka unsigned (in sogut wie allen Implementationen) und int im ersten Argument.

    Ist hier SFINAE o.ä. im Spiel?
    Oder habe ich was ganz simples übersehen?

    Wieso ich frage: Ich habe eine Implementation von dynarray geschrieben, jetzt wird aber bei Dynarray(4, 8) statt dem erwarteten Ctor der Iteratorenpaar-Ctor aufgerufen... (was natürlich zu Fehlern führt).
    Hier die beiden Ctors:

    Dynarray(size_type s,
                 value_type const& val = value_type(),
                 allocator_type const& all = allocator_type()) :
            Dynarray(s, all)
        {
            auto i = begin();
            while(i != end())
                mAllocator.construct(i++, val);
        }
    
        template<typename Iter>
        Dynarray(Iter a, Iter b, allocator_type const& all = allocator_type()):
            Dynarray(std::distance(a, b), all)
        {
            _constructWith(a, b);
        }
    

    MfG


  • Mod

    23.2.3/14 + 23.2.3/15
    Wird für gewöhnlich durch Tag-Dispatching gelöst, es existieren aber auch andere Möglichkeiten.



  • Eine interessante Frage, ich wäre nie von selbst auf die Lösung gekommen.
    <vector> von Visual C++ macht das tatsächlich mit Tag Dispatching.

    #include <iterator>
    #include <iostream>
    
    namespace
    {
    	void vector_ctor(size_t n, int element)
    	{
    		std::cout << "constructing from size\n";
    	}
    
    	//Pseudo-Iterator-Kategorie, um beim "Tag Dispatching" Integer von Iteratoren unterscheiden zu können
    	struct int_category
    	{
    	};
    
    	template <class I>
    	void real_vector_ctor(I begin, I end, int_category)
    	{
    		std::cout << "nope, integers\n";
    
    		const size_t n = begin;
    		const int element = end;
    		vector_ctor(n, element);
    	}
    
    	template <class I>
    	void real_vector_ctor(I begin, I end, std::random_access_iterator_tag) //für andere Iteratortypen natürlich äquivalent
    	{
    		std::cout << "iterators in deed!\n";
    	}
    
    	template <class I>
    	struct iterator_category
    	{
    		typedef typename std::iterator_traits<I>::iterator_category type;
    	};
    
    	template <>
    	struct iterator_category<int> //für andere Integer-Typen würde man das auch machen
    	{
    		typedef int_category type; 
    	};
    
    	template <class I>
    	void vector_ctor(I begin, I end)
    	{
    		std::cout << "here be iterators..\n";
    
    		//ob I tatsächlich ein Iterator ist, wird hier festgestellt
    		real_vector_ctor(begin, end, typename iterator_category<I>::type());
    	}
    }
    
    int main()
    {
    	vector_ctor(static_cast<size_t>(1), 1);
    	std::cout << '\n';
    
    	vector_ctor(1, 1);
    	std::cout << '\n';
    
    	int d;
    	vector_ctor(&d, &d);
    	std::cout << '\n';
    }
    
    //Erwartete Ausgabe:
    /*
    constructing from size
    
    here be iterators..
    nope, integers
    constructing from size
    
    here be iterators..
    iterators in deed!
    */
    


  • Edit: Neh, Blödsinn. So gibt es ja nur eine Fehlermeldung.


  • Mod

    Mit C++11 kann man das auch einfacher haben

    #include <iterator>
    #include <iostream>
    
    void vector_ctor(size_t n, int element)
    {
        std::cout << "constructing from size\n";
    }
    
    // iterator_traits<T>::iterator_category funktioniert nicht mit nicht-Pointer-Skalaren
    // Da der fehlerhafte Zugriff nicht im unmittelbaren Kontext (14.8.2/8) der Funktionsdeklaration stattfinden würde, kann
    // dieser Bezeichner nicht direkt für SFINAE verwendet werden
    // folglich:
    template <typename T, bool = std::is_class<T>::value || std::is_pointer<T>::value>
    struct my_iterator_traits : std::iterator_traits<T> {};
    template <typename T>
    struct my_iterator_traits<T, false> {};
    
    template <class I,
        typename std::enable_if<std::is_convertible<typename my_iterator_traits<I>::iterator_category, std::input_iterator_tag>::value, int>::type = 0>
    void vector_ctor(I begin, I end)
    {
        std::cout << "iterators indeed!\n";
    }
    
    int main()
    {
        vector_ctor(static_cast<size_t>(1), 1);
        std::cout << '\n';
    
        vector_ctor(1, 1);
        std::cout << '\n';
    
        int d;
        vector_ctor(&d, &d);
        std::cout << '\n';
    }
    

    (SFINAE würde auch mit C++03 gehen, allerdings müsste dabür die Parameterliste oder der Rückgabetyp geändert werden - was bei der Standardbibliothek unerwünscht wäre).



  • If the constructor

    template <class InputIterator>
    X(InputIterator first, InputIterator last,
    const allocator_type& alloc = allocator_type())
    

    is called with a type InputIterator that does not qualify as an input iterator, then the constructor shall not participate in overload resolution.

    Ich frage mich gerade, ob die Implementation vom gcc korrekt ist

    template<typename _InputIterator>
            vector(_InputIterator __first, _InputIterator __last,
                   const allocator_type& __a = allocator_type())
            : _Base(__a)
            {
              // Check whether it's an integral type.  If so, it's not an iterator.          
              typedef typename std::__is_integer<_InputIterator>::__type _Integral;
              _M_initialize_dispatch(__first, __last, _Integral());
            }
    

    Hier nimmt der Konstruktor strenggenommen an der overload resolution teil. Allerdings verhält er sich wie wenn er nicht teilgenommen hätte.



  • Jo, aber camper! Dann verändert sich doch die Template-Argumenten Liste!

    Edit: Ah, ne! Das ist ja der Konstruktor, für den kann man ja sowieso keine Angeben!
    Schlau, schlau! 💡 👍



  • Und außerdem geht es doch auch noch viel einfacher:

    #include <iterator> 
    #include <iostream> 
    
    void vector_ctor(size_t n, int element) 
    { 
        std::cout << "constructing from size\n"; 
    } 
    
    template <class I, 
        typename std::enable_if<not std::is_convertible<I, int>::value, int>::type = 0> 
    void vector_ctor(I begin, I end) 
    { 
        std::cout << "iterators indeed!\n"; 
    } 
    
    int main() 
    { 
        vector_ctor(static_cast<size_t>(1), 1); 
        std::cout << '\n'; 
    
        vector_ctor(1, 1); 
        std::cout << '\n'; 
    
        int d; 
        vector_ctor(&d, &d); 
        std::cout << '\n'; 
    }
    

    Ideone: http://ideone.com/dd5wd3


  • Mod

    Sone schrieb:

    Jo, aber camper! Dann verändert sich doch die Template-Argumenten Liste!

    Das ist irrelevant selbst für normale Template-Funktionen, da diese sowieso nicht als Templatetemplateparameter auftreten können.

    Sone schrieb:

    Und außerdem geht es doch auch noch viel einfacher:

    Ich glaube nicht, das irgenwo steht, dass Iteratoren nicht nach int konvertierbar sein dürfen.



  • camper schrieb:

    Sone schrieb:

    Jo, aber camper! Dann verändert sich doch die Template-Argumenten Liste!

    Das ist irrelevant selbst für normale Template-Funktionen, da diese sowieso nicht als Templatetemplateparameter auftreten können.

    Noch besser, nicht schlecht der Trick.

    camper schrieb:

    Ich glaube nicht, das irgenwo steht, dass Iteratoren nicht nach int konvertierbar sein dürfen.

    Ja, aber hundertprozentig keine Implementation wird einen Iterator nach int konvertierbar machen.
    Egal, hier eben besser:

    template <class I, 
        typename std::enable_if<std::is_class<I>::value or std::is_pointer<I>::value, int>::type = 0> 
    void vector_ctor(I begin, I end) 
    { 
        std::cout << "iterators indeed!\n"; 
    }
    

    Das einzige was enable_if hier durchgehen lässt wäre eine Klasse oder einen Zeiger. Was anderes kommt für einen Iterator sowieso nicht in Frage.


  • Mod

    Sone schrieb:

    camper schrieb:

    Ich glaube nicht, das irgenwo steht, dass Iteratoren nicht nach int konvertierbar sein dürfen.

    Ja, aber hundertprozentig keine Implementation wird einen Iterator nach int konvertierbar machen.

    Welche Implementation? Das Argument kann irgendetwas sein, was der Nutzer zusammengebastelt hat.

    Sone schrieb:

    Egal, hier eben besser:

    template <class I, 
        typename std::enable_if<std::is_class<I>::value or std::is_pointer<I>::value, int>::type = 0> 
    void vector_ctor(I begin, I end) 
    { 
        std::cout << "iterators indeed!\n"; 
    }
    

    Das einzige was enable_if hier durchgehen lässt wäre eine Klasse oder einen Zeiger. Was anderes kommt für einen Iterator sowieso nicht in Frage.

    Also geht es mit big-ints schon mal schief. Ich verstehe nicht, was einfacher daran sein soll, die falsche Frage zu stellen. Die Iterator-Überladung macht dann sinn, wenn der Parameter ein Iterator der richtigen Kategory ist. iterator_traits gibt genau darüber Auskunft.



  • camper schrieb:

    Sone schrieb:

    camper schrieb:

    Ich glaube nicht, das irgenwo steht, dass Iteratoren nicht nach int konvertierbar sein dürfen.

    Ja, aber hundertprozentig keine Implementation wird einen Iterator nach int konvertierbar machen.

    Welche Implementation? Das Argument kann irgendetwas sein, was der Nutzer zusammengebastelt hat.

    Ja, aber kein Nutzer wird einen Iterator implizit zu einem int konvertierbar machen. Das ist einfach so ... unnötig.

    Sone schrieb:

    Egal, hier eben besser:

    template <class I, 
        typename std::enable_if<std::is_class<I>::value or std::is_pointer<I>::value, int>::type = 0> 
    void vector_ctor(I begin, I end) 
    { 
        std::cout << "iterators indeed!\n"; 
    }
    

    Das einzige was enable_if hier durchgehen lässt wäre eine Klasse oder einen Zeiger. Was anderes kommt für einen Iterator sowieso nicht in Frage.

    Also geht es mit big-ints schon mal schief. Ich verstehe nicht, was einfacher daran sein soll, die falsche Frage zu stellen. Die Iterator-Überladung macht dann sinn, wenn der Parameter ein Iterator der richtigen Kategory ist. iterator_traits gibt genau darüber Auskunft.

    Al ob jemand big-ints für Größenangaben benutzt! Jetzt komm, wir entfernen uns total von der Realität.

    Allerdings werde ich wohl einfach machen was du sagst :xmas1: , ist immer besser als auf mich zu hören XD


Anmelden zum Antworten