Falsches Template wird genutzt



  • Hallo,
    für eine Art Container habe ich eine Template-Klasse geschrieben, die unter anderem einen Konstruktor hat, der die Elemente direkt übergeben bekommt (siehe http://www.c-plusplus.net/forum/p2289416#2289416), und einen, der mit zwei Iteratoren die Daten ausfüllt.
    Wenn ich allerdings einen Container der Größe 2 erstelle, möchte der Compiler immer die Iterator-Variante verwenden, was dann z.B. dazu führt, dass er sich kurz danach beschwert, dass mein Template-Parameter doch gar keinen operator* unterstützt.
    Gibt es da eine Möglichkeit, das zu ändern, oder kann ich nur einen der beiden Konstruktoren haben?



  • Nimm lieber eine std::initializer_list<T> , dann kannst du zwischen den Konstruktoren auswählen (() vs. {})


  • Mod

    Wurstinator schrieb:

    Gibt es da eine Möglichkeit, das zu ändern, oder kann ich nur einen der beiden Konstruktoren haben?

    Sicher. Zeig erst mal was du hast.



  • @ inili: Tut mir Leid, das verstehe ich nicht wirklich. Meinst du, der Iterator-Konstruktor soll bleiben und den anderen soll ich zur initializer_list ändern?

    @ camper:

    #ifndef INCLUDE_REALVECTOR_H
    #define INCLUDE_REALVECTOR_H
    
    #include <array>
    #include <utility>
    
    template <unsigned int N>
    class RealVector
    {
        public:
            RealVector();
            template <typename InputIter> 
                RealVector(InputIter first, InputIter last);
            template <typename... Args>
                RealVector(Args... args);
        private:
            std::array<double, N> numbers;
    };
    
    template <typename... T> struct allSame; 
    template <> struct allSame<> 
        : std::true_type {}; 
    template <typename T, typename... U> struct allSame<T, U...> 
        : std::is_same<allSame<T, U...>, allSame<U..., T>> {}; 
    
    template <typename T, typename... U> struct canConvert
        : allSame< std::true_type , typename std::is_convertible<U, T>::type... > {};
    
    #include "realvector.impl"
    
    #endif
    
    #include <iterator>
    
    template <unsigned int N>
    RealVector<N>::RealVector()
    {}
    
    template <unsigned int N>
    template <typename InputIter>
    RealVector<N>::RealVector(InputIter first, InputIter last)
    {
        typedef std::iterator_traits<InputIter>::iterator_category iter_cat;
        static_assert( std::is_base_of<std::input_iterator_tag, iter_cat>::value ,
                       "iterator must be of type InputIterator" );
        int n = 0;
        while (n < N && first != last)
            numbers[n] = *first++;
    }
    
    template <unsigned int N>
    template <typename... Args>
    RealVector<N>::RealVector(Args... args)
        : numbers({{args...}})
    {
        static_assert( sizeof...(args) == N , "wrong number of arguments" );
        static_assert( canConvert<double, Args...>::value , "arguments have to be of type double" );
    }
    


  • Genau, so meine ich das.

    template <unsigned int N>
    struct RealVector {
      RealVector();
      template <typename InputIter> RealVector(InputIter first, InputIter last);
      RealVector(std::initializer_list<double> inili) {
        assert(inili.size() == N);
        ...
      }
    };
    
    int main()
    {
      RealVector<2> r1; // default ctor
      std::array<int, 4> arr{1,2,3,4};
      RealVector<2> r2(arr.begin(), arr.end()); // iterator ctor
      RealVector<2> r3{1,2}; // initializer list
    
      RealVector<7> r4{1,2,3,4,5,6,7}; // initializer list
    }
    

    In deinem Fall (wenn die Elemente vom Typ double sind) kann man beim Iteratorenkonstruktor natürlich auf die Existenz des Dereferenzierungsoperators testen, aber das halte ich nicht für eine gute Idee. Die Initializerlist ist genau für solche Fälle da.


  • Mod

    Eine bisschen SFINAE hilft. initializer_list mag ich nicht.
    Die fehlende Überprüfung der Größe bereits beim Compilieren machen diese hier unattraktiv. Zudem wird damit der falsche Konstruktor repariert.
    Und die Tatsache, dass die Klammerwahl eine Rolle spielen kann, spricht auch dagegen, diese Art Subtilität halte ich für einen schweren Fehler des Standardkomitees.

    template <unsigned int N>
    class RealVector
    {
        public:
            RealVector();
            template <typename InputIter, typename std::enable_if<std::is_convertible<typename std::iterator_traits<InputIter>::type, double>::value
                && std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<InputIter>::type>::value, int>::type = 0>
                RealVector(InputIter first, InputIter last);
            template <typename... Args>
                RealVector(Args... args);
        private:
            std::array<double, N> numbers;
    };
    
    template <unsigned int N>
    template <typename InputIter, typename std::enable_if<std::is_convertible<typename std::iterator_traits<InputIter>::type, double>::value
        && std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<InputIter>::type>::value, int>::type>
    RealVector<N>::RealVector(InputIter first, InputIter last)
    {
        typedef typename std::iterator_traits<InputIter>::iterator_category iter_cat;
        static_assert( std::is_base_of<std::input_iterator_tag, iter_cat>::value ,
                       "iterator must be of type InputIterator" );
        int n = 0;
        while (n < N && first != last)
            numbers[n] = *first++;
    }
    


  • @ inili: Ich mag initializer_lists nicht und benutze sie daher ungerne, da sie nichts Konstruktives bringen (in diesem Fall) und ungewöhnliche Syntax haben.

    @ camper: Wenn ich also auf std::enable_if<...>::type zugreife, dieser aber nicht existiert, schlägt das Template sozusagen fehl?

    edit:
    Gibt es eigentlich eine Möglichkeit, den Test von enable_if auszulagenr, damit es übersichtlicher wird?

    template <typename Iter> struct isInputIter
        : std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<Iter>::iterator_category> {};
    

    Sowas in der Art. Aber wenn ich das dann einfüge, tritt wieder das alte Problem auf, dass der Compiler versucht, die Zahlen als Iterator zu interpretieren. Ergo sagt er mir, dass double keinen Member namens iterator_category hat.


Anmelden zum Antworten