Templates und Operator



  • Kann man es irgendwie schaffen, dass der Compiler das hier annimmt?

    Das kommt darauf an. Falls sich dein Compiler nur über die mangelnde Zugriffserlaubnis im ersten Template-Op- beschwert, gibt es drei Lösungen:
    a) mach den op- zum friend

    template <class T>
    class Vector2d
    {
    //...
    template <class U1>
    friend const Vector2d<U1> operator-(const Vector2d<U1> &left,const Vector2d<U1> &right);
    };
    template <class U1>
    const Vector2d<U1> operator-(const Vector2d<U1> &left,const Vector2d<U1> &right)
    {
        return Vector2d<U1>(left.x_-right.x_,left.y_-right.y_);
    }
    

    b) implementier den op- über die public-Schnittstelle - Variante a

    template <class T>
    class Vector2d
    {
    public:
        T getX() const {return x_;}
        T getY() const {return y_;}
    //...};
    
    template <class U1>
    const Vector2d<U1> operator-(const Vector2d<U1> &left,const Vector2d<U1> &right)
    {
        return Vector2d<U1>(left.getX()-right.getX(),left.getY()-right.getY());
    }
    

    c) implementier den op- über den op-= (die gute Variante):

    template <class T>
    class Vector2d
    {
    public:
        T getX() const {return x_;}
        T getY() const {return y_;}
        template <class U1> 
        Vector2d& operator-=(const Vector2d<U1>& rhs)
        {
            x_ -= rhs.getX();
            y_ -= rhs.getY();
            return *this;
        }
    //...
    };
    
    template <class U1>
    const Vector2d<U1> operator-(const Vector2d<U1> &left,const Vector2d<U1> &right)
    {
        Vector2d<U1> temp(left);
        temp -= right;
        return temp;
    }
    

    Falls du hingegen einen Compiler hast, der sich darüber beschwert, dass eine Vorlagenfunktion op- bereits definiert wurde (wie z.B. es der VC 6 tut), dann wird es etwas haariger.

    Obwohl ich deine konkrete Situation für sinnbefreit halte, hier mal eine mögliche Lösung für dein Problem, Schritt für Schritt:
    Problem: Der Compiler beschwert sich, dass eine Vorlagenfunktion op- bereits existiert -> er kann zwischen den beiden Versionen nicht unterscheiden.
    Lösung: Wähle die generellere Version.

    // Dieses Template ist genereller als die zweite Variante.
    // Also behalten wir diesen:
    template <class U1,class U2>
    const Vector2d<U1> operator-(const Vector2d<U1> &left,const Vector2d<U2> &right);
    

    Für den Fall das U1 = U2 gilt, soll Algo A sonst Algo B verwendet werden.
    2. Problem: Wie prüft man die Bedingung U1 = U2 zur Compilezeit?
    Lösung:

    // value ist true, falls T1 = T2. 
    // false sonst:
    // ACHTUNG: MSVC only! Kein gültiges Standard-C++. 
    // Explizite Templatespezialisierung ist nur auf Namespacebene erlaubt.
    template<class T1,class T2>
    struct SameType
    {
    private:
      template<class U>
      struct In 
      { enum { value = false }; };
    
      template<>
      struct In<T1>
      { enum { value = true };  };
    
    public:
      enum { value = In<T2>::value };
    };
    

    Abhängig vom Wert von SameType<T1, T2>::value müssen wir jetzt nur noch den passenden Algo auswählen und im op- zu diesem forwarden.

    In C++ sieht das dann so aus:

    // macht aus einem booelschen Wert (true, false)
    // ein Typ. Erlaubt damit also Überladung auf Basis des Ergebnisses einer 
    // Bedingung.
    template <bool> struct Bool2Type {}; 
    
    // Implementation für U1 = U2
    template <class U1>
    const Vector2d<U1> opMinusImpl(const Vector2d<U1>& left, 
                                   const Vector2d<U1>& right, 
                                   Bool2Type<true> )
    {
        return Vector2d<U1>(0,0);
    }
    
    // Implementation für U1 != U2
    template <class U1, class U2>
    const Vector2d<U1> opMinusImpl(const Vector2d<U1>& left, 
                                   const Vector2d<U2>& right, 
                                   Bool2Type<false> )
    {
        return Vector2d<U1>(left.getX()-right.getY(),left.getX()-right.getY());
    }
    template <class U1,class U2>
    const Vector2d<U1> operator-(const Vector2d<U1> &left,const Vector2d<U2> &right)
    {
        // U1 == U2 testen und zur entsprechenden Funktion forwarden
        return opMinusImpl(left, right, Bool2Type<SameType<U1, U2>::value>());
    }
    

    Fertig.

    [ Dieser Beitrag wurde am 27.05.2003 um 19:12 Uhr von HumeSikkins editiert. ]



  • Vielen Dank für deine Hilfe!
    "Sinnbefreit"-lol
    Du meinst also, dass ich es nicht erlauben sollte, dass man einen float-Vektor mit einem int-Vektor addiert? Aber ich denke, dass man das dann selber konvertieren soll, oder?

    Muss ich, wenn ich jetzt den *-Operator überlade, für beide Möglichkeiten einen Operator überladen?

    Vec=Vec4;
    Vec=4
    Vec;

    Dann müsste ich einmal den skalaren Typ rechts und einmal links haben, oder?



  • Hallo,
    das sinnbefreit bezog sich auf die Art und Weise wie du den op- implementiert hast. Für gleiche Typen lieferst du einen Nullvektor. Für ungleiche machst du komponentenweise Minus. Das macht keinen Sinn.

    Einmal komponentenweise Minus reicht. Dann sparst du dir auch das gehampel mit den beiden Template-Op-.

    Muss ich, wenn ich jetzt den *-Operator überlade, für beide Möglichkeiten einen Operator überladen?

    Mit dem blöden VC 6.0 ist folgendes wohl das einfachste:

    template <class T>
    class Vector2d
    {
    public:
        // Initialisierungskonstruktor
        Vector2d(T a = 0, T b = 0) 
        : x_(a)
        , y_(b) 
        {}
    
        // Konstruktor für das Initialisieren mit "kompatiblen" Vektoren
        template <class U>
        Vector2d(const Vector2d<U>& rhs) 
            : x_(rhs.getX())
            , y_(rhs.getY()) 
        {}
    
        // Ein Template-Ctor ist *niemals* ein Copy-Ctor
        // Der compilergenerierte würde auch ausreichen.
        Vector2d(const Vector2d& rhs) : x_(rhs.x_), y_(rhs.y_) 
        {}
    
        // Zuweisung von "kompatiblen" Vektoren
        template <class U>
        Vector2d<T>& operator=(const Vector2d<U>& rhs)
        {
            x_ = rhs.getX();
            y_ = rhs.getY();
            return *this;
        }
        Vector2d& operator=(const Vector2d& rhs)
        {
            x_ = rhs.x_;
            y_ = rhs.y_;
            return *this;
        }
        T getX() const {return x_;}
        T getY() const {return y_;}
    
        // Die kombinierten Operatoren sind 
        // *immer* Member, verändern das aktuelle Objekt und liefern 
        // eine Referenz.
        template <class U>
        Vector2d& operator+=(const U& rhs)
        {
            return *this += Vector2d(rhs);
        }
        template <class U>
        Vector2d& operator*=(const U& rhs)
        {
            return *this *= Vector2d(rhs);
        }
        Vector2d& operator+= (const Vector2d& rhs)
        {
            x_ += rhs.x_;
            y_ += rhs.y_;
            return *this;
        }
        Vector2d& operator*= (const Vector2d& rhs)
        {
            x_ *= rhs.x_;
            y_ *= rhs.y_;
            return *this;
        }
    
        // Für Vec op Skalar oder Vec
        template <class U>
        const Vector2d operator+(const U& rhs)
        {
            return Vector2d<T>(*this) += rhs;
        }
        template <class U>
        const Vector2d operator*(const U& rhs)
        {
            return Vector2d<T>(*this) *= rhs;
        }
    private:
        T x_, y_;
    };
    
    // Für Skalar op Vec
    template <class T, class U>
    const Vector2d<T> operator+(const U& lhs, const Vector2d<T>& rhs)
    {
        return Vector2d<T>(rhs) += lhs;
    }
    
    template <class T, class U>
    const Vector2d<T> operator*(const U& lhs, const Vector2d<T>& rhs)
    {
        return Vector2d<T>(rhs) *= lhs;
    }
    
    int main()
    {
        Vector2d<double> dVec(1.0, 2.0);
        Vector2d<float> fVec(dVec);
        fVec *= dVec;
        Vector2d<double> q = dVec * 3.0;
        q = 3.0 * q;
        q = 3 + dVec;
        q = dVec + 3;
        q *= 3.0;
        dVec + fVec;
    }
    


  • wieso weist du auf den "bloeden vc6" hin? wie wuerdest du die multiplikation mit einem skalar sonst kommutativ machen?



  • hast du aber nicht noch einen Konstruktor für einen skalaren Typ vergessen?
    Du hast z.B. so etwas:

    ctemplate <class U>
        Vector2d& operator+=(const U& rhs)
        {
            return *this += Vector2d(rhs);
        }
        template <class U>
        Vector2d& operator*=(const U& rhs)
        {
            return *this *= Vector2d(rhs);
        }
    

    Dann bräuchte man ja aber auch einen Konstruktor wie diesen, oder?

    template <class U> CVector2D(const U &scalar) 
            : x(scalar),y(scalar) {}
    


  • oder? Sonst kann es ja nicht funktionieren!?!



  • ich weiß jetzt, warum deine Version kompilert wurde, aber falsch gearbeitet hat:
    Es lag an dem Konstruktor:

    Vector2d(T a = 0, T b = 0) : x_(a), y_(b) {}
    

    Hier wurde dann der Konstruktor mit a=rhs und b=0

    template <class U>
        Vector2d& operator*=(const U& rhs)
        {
            return *this *= Vector2d(rhs);
        }
    


  • Bei mir geht es jetzt.

    template <class T> class CVector2D
    {
        public: 
            T x,y;
            CVector2D(const T x,const T y) { this->x=x ; this->y=y;}
            CVector2D(const T xy) { this->x=xy ; this->y=xy;}
    
            template <class U> CVector2D(const CVector2D<U> &vec) : x(vec.x),y(vec.y) {}
            //Copy-Konstruktor für den gleichen Typ
            CVector2D(const CVector2D &vec) : x(vec.x),y(vec.y) {}
    
            void SetPosition(const T x,const T y) {this->x=x; this->y=y; }
            void SetVector(const T x,const T y) {this->x=x; this->y=y; }
    
            T GetX(void) const {return x;}
            T GetY(void) const {return y;}
    
            void Normalize(void)
            {
                double length=sqrt(x*x+y*y);
                if(length!=0)
                {
                    double multiplicator=1.0f/length;
                    x*=multiplicator;
                    y*=multiplicator;
                }
            }
    
            // Zuweisung von unterschiedlichen Vektoren
            template <class U1>
            CVector2D<T>& operator=(const CVector2D<U1> &right)
            {
                x = right.x;
                y = right.y;
                return *this;
            }
    
            //Zuweisung für gleiche Typen
            CVector2D& operator=(const CVector2D &right)
            {
                x = right.x;
                y = right.y;
                return *this;
            }
    
            template <class U> CVector2D& operator+=(const U& right)
            {
                return *this += CVector2D(right);
            }
    
            template <class U> CVector2D& operator*=(const U& right)
            {
                return *this *= CVector2D(right);
            }
    
            CVector2D& operator+=(const CVector2D& right)
            {
                x+=right.x;
                y+=right.y;
                return *this;
            }
    
            CVector2D& operator*= (const CVector2D& right)
            {
                x*= right.x;
                y*= right.y;
                return *this;
            }
    
            template <class U>
            const CVector2D operator+(const U& rhs)
            {
                return CVector2D<T>(*this) += rhs;
            }
            template <class U>
            const CVector2D operator*(const U& rhs)
            {
                return CVector2D<T>(*this) *= rhs;
            }
    
    };
    
    template <class T, class U>
    const CVector2D<T> operator+(const U& lhs, const CVector2D<T>& rhs)
    {
        return CVector2D<T>(rhs) += lhs;
    }
    
    template <class T, class U>
    const CVector2D<T> operator*(const U& lhs, const CVector2D<T>& rhs)
    {
        return CVector2D<T>(rhs) *= lhs;
    }
    

    Aber ein Problem ist, dass ich soetwas nicht machen kann:
    Vec=Vec*Vec*5;
    Aber das hier geht:
    Vec=Vec*Vec;

    Dann sagt der Compiler mir:

    error C2678: Binaerer Operator '*' : Kein Operator definiert, der einen linksseitigen Operator vom Typ 'const class CVector2D<double>' akzeptiert (oder keine geeignete Konvertierung moeglich)



  • wieso weist du auf den "bloeden vc6" hin?

    Weil der VC6

    // in der Klasse Vector<T>
    template <class U>
    Vector<T>& operator*= (const Vector<T>& rhs);
    template <class U>
    Vector<T>& operator*= (const U& rhs);
    

    für U = Vector<T> nicht unterscheiden kann. Und das ist hier etwas nervig.

    wie wuerdest du die multiplikation mit einem skalar sonst kommutativ machen?

    Darauf komme ich zurück. Bastle gerade an einem Beispiel mit "enablern" (dazu habe ich auch noch eine Generalisierung der Dimension im Kopf. Leider dafür aber im Moment keine Zeit).



  • Darauf komme ich zurück.

    Oder auch nicht. Der VC unterstützt das Konzept von enablern nicht. Und über einen Workaround möchte ich im Moment nicht nachdenken.

    Auf die schnelle bekomme ich mit dem VC 6 eine Vektorklasse hin, die über den op* das Kreuzprodukt zweier kompatibler Vektoren berechnen kann (Vektor<T> * Vektor<U> ) sowie skalare Multiplikation über den op* erlaubt. Diese ist aber nur eingeschränkt möglich. Für einen Vektor<T> muss der Skalar-Wert vom Typ T sein.

    Die Generalisierung auf kompatible Skalare vom Typ U verlangt beim VC 6 einen operator* der Form:

    template <class T, class U>
    Vector<typename PromotionTraits<T, U>::Result> 
    operator* (const T& lhs, const U& rhs);
    

    und ein solcher ist mir ohne die enabler-Technik (erlaubt das Entfernen einer Template-Funktion aus der Menge der möglichen Überladungskandidaten) viel zu greedy 🙂


Anmelden zum Antworten