kurze Fragen zu template-Klasse mit namespace



  • Hi, ich habe eine kleine Vektorenklasse, wo es wahrscheinlich nicht viel zu sagen gibt. (Ich hoffe, denn die Übungsaufgaben sind überall nur in drei Dimensionen).
    Ich habe einige Funktionen in einem namespace, damit die sich gegenseitig kennen habe ich den namespace vor die Klasse getan und davor ein

    template<typename T>
    class Vector_T; //um Vector_T bekannt zu machen
    

    Ich denke, das ist in Ordnung?

    Nun kann man ja einen Vector_T<int> machen, aber manche Funktionen wie die Länge des Vector geben keinen Sinn, wenn man auch einen intzurück gibt. Wäre es nicht besser, dort statt dem template-Typ gleich einen mit höchster Genauigkeit wie einem doublezurück zu geben?

    template<typename T>
    class Vector_T; //um Vector_T bekannt zu machen
    
    namespace Vector
    {
    	template<typename val_t>
    	val_t magnitude(const Vector_T<val_t>& vT)
    	{
    		val_t sum = 0;
    		for (const auto& v : vT.vector())
    			sum += v * v;
    
    		return std::sqrt(sum);
    	}
    
    	template<typename val_t>
    	val_t scalarProduct(const Vector_T<val_t>& vTA, const Vector_T<val_t>& vTB)
    	{
    		if (vTA.size() != vTB.size())
    			throw std::exception("scalarProduct(Vector_T, Vector_T): not same Dimension");
    
    		val_t v = 0;
    		for (size_t i = 0; i < vTA.size(); ++i)
    			v += vTA[i] * vTB[i];
    
    		return v;
    	}
    
    	template<typename val_t>
    	Vector_T<val_t> vectorProduct(const Vector_T<val_t>& vTA, const Vector_T<val_t>& vTB)
    	{
    		if (vTA.size() != vTB.size())
    			throw std::exception("vectorProduct(Vector_T, Vector_T): not same Dimension");
    
    		std::vector<val_t> vecTA = vTA.vector();
    		std::vector<val_t> vecTB = vTB.vector();
    		//Vektoren verdoppeln für Kreuzmultiplikation
    		vecTA.insert(vecTA.end(), vecTA.begin(), vecTA.end());
    		vecTB.insert(vecTB.end(), vecTB.begin(), vecTB.end());
    		//über Kreuz multiplizieren
    		std::vector<val_t> vecA, vecB;
    		for (std::size_t i = 1; i < vecTA.size() - 2; ++i)
    		{
    			vecA.push_back(vecTA[i] * vecTB[i + 1]);
    			vecB.push_back(vecTB[i] * vecTA[i + 1]);
    		}
    		//Vektoren subtrahieren
    		for (std::size_t i = 0; i < vecA.size(); ++i)
    			vecA[i] = vecA[i] - vecB[i];
    
    		return Vector_T<val_t>(vecA);
    	}
    
    	template<typename val_t>
    	val_t angle(const Vector_T<val_t>& vTA, const Vector_T<val_t>& vTB)
    	{
    		const val_t n = Vector::scalarProduct(vTA, vTB);
    		const val_t m = Vector::magnitude(vTA) * Vector::magnitude(vTB);
    		return std::acos(n / m);
    	}
    }
    
    template<typename T>
    class Vector_T
    {
    public:
    	using val_t = T;
    	using size_t = std::size_t;
    
    	Vector_T() = default;
    	Vector_T(const std::vector<val_t>& vec) 
    		: vector_{ vec } {}
    	Vector_T(const std::initializer_list<val_t>& list)
    		: vector_{ list } {}
    
    	std::vector<val_t> vector() const { return vector_; }
    	size_t size() const { return vector_.size(); }
    
    	val_t operator()(const size_t idx) const
    	{
    		if (idx >= size())
    			throw std::out_of_range("operator()");
    
    		return vector_[idx];
    	}
    	val_t& operator()(const size_t idx)
    	{
    		if (idx >= size())
    			throw std::out_of_range("operator()");
    
    		return vector_[idx];
    	}
    
    	val_t operator[](const size_t idx) const
    	{
    		if (idx >= size())
    			throw std::out_of_range("operator[]");
    
    		return vector_[idx];
    	}
    	val_t& operator[](const size_t idx)
    	{
    		if (idx >= size())
    			throw std::out_of_range("operator[]");
    
    		return vector_[idx];
    	}
    
    	Vector_T& operator+=(const Vector_T& vT)
    	{
    		if (size() != vT.size())
    			throw std::exception("operator+=(Vector_T): not same Dimension");
    
    		for (size_t i = 0; i < size(); ++i)
    			(*this)[i] += vT[i];
    
    		return (*this);
    	}
    
    	Vector_T operator+(const Vector_T& vT) const
    	{
    		if (size() != vT.size())
    			throw std::exception("operator+(Vector_T): not same Dimension");
    
    		Vector_T nvT(vector_);
    		for (size_t i = 0; i < size(); ++i)
    			nvT[i] = (*this)[i] + vT[i];
    
    		return nvT;
    	}
    
    	Vector_T& operator*=(const val_t& v)
    	{
    		for (size_t i = 0; i < size(); ++i)
    			(*this)[i] *= v;
    
    		return (*this);
    	}
    
    	Vector_T operator*(const val_t& v) const
    	{
    		Vector_T vT(vector_);
    		for (size_t i = 0; i < size(); ++i)
    			vT[i] = vT * v;
    
    		return vT;
    	}
    
    	val_t mag() const
    	{
    		return Vector::magnitude((*this));
    	}
    
    	val_t angle(const Vector_T<val_t>& vT) const
    	{
    		return Vector::angle((*this), vT);
    	}
    
    	val_t scalar(const Vector_T<val_t>& vT) const
    	{
    		return Vector::scalarProduct((*this), vT);
    	}
    
    	Vector_T cross(const Vector_T<val_t>& vT) const
    	{
    		return Vector::vectorProduct((*this), vT);
    	}
    
    	Vector_T& cross(const Vector_T<val_t>& vT)
    	{
    		(*this) = Vector::vectorProduct((*this), vT);
    		return (*this);
    	}
    
    private:
    	std::vector<val_t> vector_;
    };
    
    template <typename val_t>
    std::ostream& operator<<(std::ostream& stream, const Vector_T<val_t>& vT)
    {
    	std::size_t i = 0;
    	for (const auto& v : vT.vector())
    	{
    		stream << "[" << i++ << "]: ";
    		stream << v << '\n';
    	}
    }
    


  • Mehrere Template Argumente dürfen angegeben werden.



  • Ich dachte jetzt eher an sowas

            double mag() const
    	{
    		return Vector::magnitude((*this));
    	}
    
            template<typename val_t>
    	double magnitude(const Vector_T<val_t>& vT)
    	{
    		double sum = 0;
    		for (const auto& v : vT.vector())
    			sum += v * v;
    
    		return std::sqrt(sum);
    	}
    

    Man braucht doch gar keine Auswahl von template-Typen? Man nimmt bei Gleitkomma-Rückgaben den mit der höchsten Genauigkeit.
    Allerdings würde mich interessieren, wie man mehrere template-Argumente angibt.



  • mehrere Templatetypen gehen z.B. so:

     template<typename ret_t, typename val_t>
    	ret_t magnitude(const Vector_T<val_t>& vT)
    	{
    		ret_t sum = 0;
    		for (const auto& v : vT.vector())
    			sum += v * v;
    
    		return std::sqrt(sum);
    	}
    

    braucht man so was für die Länge eines Vektors? Gute Frage. Vlt will man mal einen Typen mit einer höheren genauigkeit einbinden, oder man muss Aufgrund des Zielsystems geziehlt mit 32bit oder 64bit Darstellungen arbeiten, oder man möchte explizit float nehmen, weil einem die Genauigkeit nicht interessiert, aber eher Sparsam mit Ressourcen umgehen muss.
    Also, mir würden Gründe dafür einfallen, wobei man im Fall von anderen Typen natürlich auch ´´´std::sqrt´´´ nicht verwenden dürfte und ich persönlich den Fall wahrscheinlich nicht abdecken würde.
    Was ich persönlich interessant fände, wäre der Fall für zum Beispiel Komplexe Zahlen, wobei ich gestehen muss, dass ich mich mit std::complex noch überhaupt nicht beschäftigt habe.



  • Für einige Rückgaben einen eigenen template-Typ nehmen finde ich gut. Dadurch bleibt die Klasse "universell", also jedenfalls in diesem bescheidenen Umfang, und man muss sich nicht fragen was man zB mit int = Wurzel aus 2 macht.

    Im Moment habe ich noch eine kleine Blockade, wie dies im konkreten Fall ausschaut, aber das werde ich später ja sehen. Danke jedenfalls.



  • @zeropage sagte in kurze Fragen zu template-Klasse mit namespace:

    Ich dachte jetzt eher an sowas

            double mag() const
    	{
    		return Vector::magnitude((*this));
    	}
    
            template<typename val_t>
    	double magnitude(const Vector_T<val_t>& vT)
    	{
    		double sum = 0;
    		for (const auto& v : vT.vector())
    			sum += v * v;
    
    		return std::sqrt(sum);
    	}
    

    In diesem Fall nimmt man den Typ, den man auch für den Skalarenkörper benutzt hat. Dazu würde ich die Euklidische Norm als Wurzel der inneren Produktes definieren. Die Templates sind mit dem Default Parameter Vector_T<T> definiert, so dass man Deinen Vektor Typen nicht jedesmal mit angegeben muss, aber im Prinzip sollte es mit jedem Vektortypen funktionieren.

    template <typename T, class Vector = Vector_T<T>>
    T inner_product (const Vector& v1, const Vector& v2) {
        if (v1.size() != v2.size()) throw std::runtime_error("dimension mismatch");
        T iprod ({});
    
        for (Vector<T>::size_type i = 0, e = v1.size(); i != e; ++i) {
            iprod += v1[i] * v2[i];
        }
    
        return iprod;
    }
    
    template <typename T, class Vector = Vector_T<T>>
    T euclidean_norm (const Vector& v) {
        T mag = inner_product(v, v);
    
        using std::sqrt;
    
        return sqrt(mag);
    }
    


  • Schau dir std::basic_string und std::char_traits an. Das selbe Prinzip kannst du auch für dein Vektor-Template verwenden.

    Ansonsten könntest du natürich auch einfach std::conditional_t<std::is_floating_point<T>(), T, double> verwenden.
    Das wäre dann float für Vector_T<float> und long double für Vector_T<long double> und für alle anderen eben double.



  • @john-0
    Ui, viel neues Zeug. Alleine, ob ich überhaupt mit meinem Wissen mir die mathematischen Ausdrücke wie die Euklidische Norm als Wurzel der inneren Produktes definieren anlernen kann.

    Und der Code sieht für mich aus wie Raketenwissenschaft, wo ich mir erst mal überlegen muss, wie ich den in eine für mich lesbare Form umformuliere. Das soll keine Kritik sein, sondern das ich im Moment damit leicht überfordert bin 😉 Kann aber sein, das das nächste Woche schon wieder anders auschaut. Danke.

    @hustbaer
    Das sieht schon übersichtlicher aus 😉 Anschauen werde ich es mir. Vielleicht kapiere ich sogar das Prinzip. Danke



  • @zeropage sagte in kurze Fragen zu template-Klasse mit namespace:

    @john-0
    Ui, viel neues Zeug. Alleine, ob ich überhaupt mit meinem Wissen mir die mathematischen Ausdrücke wie die Euklidische Norm als Wurzel der inneren Produktes definieren anlernen kann.

    Das was Du aus dem normalen Leben gewohnt bist, ist der euklidische Raum |R³. Die Metrik (Abstand) die Du aus Deinem normalen Leben gewohnt bist, wird durch die euklidische Norm induziert, d.h. d(a,b)=abd(\vec{a},\vec{b})=\|a-b\| der Abstand dd ist gleich der Norm der Differenz der beiden Vektoren. Das ist nicht die einzig mögliche Norm oder die einzig mögliche Metrik, es gibt noch diverse andere Möglichkeiten. Wichtig ist nun, es gibt in einem Vektorraum das innere Produkt (auch Skalarprodukt, englisch inner product, dot product oder scalar product genannt)

    xy=x,y=i=1nxiyi=x1y1++xnyn\vec{x}\cdot\vec{y}=\langle\vec{x},\vec{y}\rangle = \sum_{i=1}^n x_i y_i=x_1y_1+ \ldots+x_n y_n

    definiert. Das innere Produkt eines Vektors mit sich selbst ist

    x,x=i=1nxi2.\langle \vec{x},\vec{x}\rangle = \sum_{i=1}^n x_i^2\text{.}

    Wie Du selbst geschrieben hast ist der Betrag (Länge) des Vektors gerade

    x=i=1nxi2.|\vec{x}| = \sqrt{\sum_{i=1}^n x_i^2}\text{.}

    D.h. man kann den Betrag des Vektors leicht über das innere Produkt des Vektors mit sich selbst definieren

    x=i=1nxi2=x,x.|\vec{x}| = \sqrt{\sum_{i=1}^n x_i^2}=\sqrt{\langle\vec{x},\vec{x}\rangle}\text{.}

    Wie der Name vermuten lässt gibt es auch noch ein äußeres Produkt (auch dyadisches Produkt genannt).

    xyT=(x1xn)(y1yn)=(x1y1x1ynxny1xnyn)\vec{x}\cdot\vec{y}^{\,\text{T}} = \left( \begin{array}{c}x_1\\\vdots\\x_n\end{array}\right)\cdot\left(\begin{array}{ccc}y_1 & \cdots & y_n\end{array} \right) = \left(\begin{array}{ccc}x_1y_1&\cdots&x_1y_n\\\vdots&\ddots&\vdots\\x_ny_1&\cdots&x_ny_n\end{array}\right)

    Das „T“ signalisiert, dass es sich um einen transponierten Vektor handelt. Das Ergebnis ist eine quadratische Matrix.

    P.S. Es wäre hilfreich, wenn Du mal schreiben könntest, was für ein mathematisches Vorwissen Du hast. Dann kann man darauf besser Rücksicht nehmen.

    Nachtrag
    Ein metrischer Raum (X,d)(X,d) ist ein Paar aus einer Menge XX und einer Funktion d:X×XR0+d: X\times X\to \mathbb{R}_0^+ (Metrik bzw. Abstandsfunktion genannt).
    Die Metrik muss folgende Axiome erfüllen.
    Positivität:

    d(x,y)0, und d(x,y)=0 genau fuer x=yd(x,y)\ge 0 \text{, und } d(x,y)=0 \text{ genau fuer } x=y

    Symmetrie:

    d(x,y)=d(y,x)d(x,y)=d(y,x)

    Dreiecksungleichung:

    d(x,y)+d(y,z)d(x,z)d(x,y)+d(y,z) \ge d(x,z)

    für alle x,y,zXx,y,z \in X



  • Um die Frage Mathe-Kenntnisse zu beantworten, Abitur habe ich keins. Trigonometrie habe ich noch mitbekommen. Aber gerade Vektoren und die komplexen Zahlen nicht mehr.

    Schaue mir aber gerne Mathe-Vorlesungen auf YouTube an, auch wenn ich nur die Hälfte davon verstehe, finde ich immer extrem spannend.

    Ich habe eine ungefähre Vorstellung, aber keine Ahnung 😉



  • @zeropage sagte in kurze Fragen zu template-Klasse mit namespace:

    Um die Frage Mathe-Kenntnisse zu beantworten, Abitur habe ich keins. Trigonometrie habe ich noch mitbekommen. Aber gerade Vektoren und die komplexen Zahlen nicht mehr.

    Ok, dann ist das ein ganz anderer Hintergrund, gut zu wissen für weitere Antworten.


Log in to reply