Przeciążanie operatora= w klasie szablonowej

0

Witam,
napotkałem na pewien problem chcąc przeciążyć operator = . Wzorowałem się trochę na tym Jak przeciążyć operator strumienia w klasie szablonowej i Przeładowywanie operatorów
Kod mam taki:

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

template <int m_przed, int m_po> class Klasa; //deklaracja szablonu klasy
template <int m_przed, int m_po> ostream& operator<< (ostream &, const Klasa<m_przed, m_po> &);
template <int m_przed, int m_po> Klasa<m_przed, m_po>& operator= (const Klasa<m_przed, m_po> &);


template <int m_przed, int m_po>
class Klasa{
private:
	string s_przed, s_po;
	
public:
	Klasa(string przed, string po){
		s_przed = przed;
		s_po = po;
	}

	friend ostream& operator<< <>(ostream &, const Klasa<m_przed, m_po> &);
	friend Klasa<m_przed, m_po>& operator= <>(const Klasa<m_przed, m_po> &);
};

template <int m_przed, int m_po> ostream& operator<< (ostream & os, const Klasa<m_przed, m_po> &l){
	os << l.s_przed << "." << l.s_po << endl;
	return os;
}

template <int m_przed, int m_po> Klasa<m_przed, m_po>& operator= (const Klasa<m_przed, m_po> &l){
	s_przed = l.s_przed;
	s_po = l.s_po;
	return *this;
}


int main(){
	Klasa<2,3> a("12", "654");
	Klasa<2,1> b("22", "1");
	cout << a;
	b = a;
	//cout << b + a;


	return 0;
}

Operator<< mi działa, zaś problem mam z operatorem = i pewnie problemy będą również z operatorami arytmetycznymi. Kompiluje to w VisualStudio 11 i nie podoba mu się:
template <int m_przed, int m_po> Klasa<m_przed, m_po>& operator= (const Klasa<m_przed, m_po> &); - error C2801: 'operator =' must be a non-static member
oraz w linii gdzie zamykane jest ciało operatora pokazuje fatal error C1903: unable to recover from previous error(s); stopping compilation.
Nie potrafię sobie z tym poradzić, prosze o pomoc.

0

Pamiętaj o tym, że dwie różne specjalizacje szablonu są dwiema zupełnie innymi klasami.

0

Operator przypisania musi być składnikiem klasy (niestatycznym), nie może być funkcją (zaprzyjaźnioną czy nie).

template <int m_przed, int m_po>
class Klasa{
        Klasa<m_przed, m_po>& operator=(const Klasa<m_przed, m_po> &);
};
 
template <int m_przed, int m_po>
Klasa<m_przed, m_po>& Klasa<m_przed, m_po>::operator=(const Klasa<m_przed, m_po> &l)
{
    ...
0

Dziękuję za zainteresowanie. Teraz kod mam taki, w którym jest jeszcze jeden problem:

 #include <iostream>
#include <sstream>
#include <string>
using namespace std;

template <int m_przed, int m_po> class Klasa; //deklaracja szablonu klasy
template <int m_przed, int m_po> ostream& operator<< (ostream &, const Klasa<m_przed, m_po> &);
//template <int m_przed, int m_po> Klasa<m_przed, m_po>& operator= (const Klasa<m_przed, m_po> &);


template <int m_przed, int m_po>
class Klasa{
private:
	string s_przed, s_po;
	
public:
	Klasa(string przed, string po){
		s_przed = przed;
		s_po = po;
	}

	friend ostream& operator<< <>(ostream &, const Klasa<m_przed, m_po> &);
	Klasa<m_przed, m_po>& operator=(const Klasa<m_przed, m_po> &);
};

template <int m_przed, int m_po> ostream& operator<< (ostream & os, const Klasa<m_przed, m_po> &l){
	os << l.s_przed << "." << l.s_po << endl;
	return os;
}

template <int m_przed, int m_po>
Klasa<m_przed, m_po>& Klasa<m_przed, m_po>::operator=(const Klasa<m_przed, m_po> &l){
	s_przed = l.s_przed;
	s_po = l.s_po;
	return *this;
}


int main(){
	Klasa<2,3> a("12", "654");
	Klasa<2,1> b("22", "1");
	cout << a;
	b = a;
	//cout << b + a;


	return 0;
}

Nadal nie chce się kompilować, a o to co wyświetla:

d5b.cpp(43): error C2679: binary '=' : no operator found which takes a right-hand operand of type 'Klasa<m_przed,m_po>' (or there is no acceptable conversion)
1> with
1> [
1> m_przed=2,
1> m_po=3
1> ]
1> c:\users\lukasz\documents\moje programy\paradygmaty\d5b\d5b\d5b.cpp(23): could be 'Klasa<m_przed,m_po> &Klasa<m_przed,m_po>::operator =(const Klasa<m_przed,m_po> &)'
1> with
1> [
1> m_przed=2,
1> m_po=1
1> ]
1> while trying to match the argument list '(Klasa<m_przed,m_po>, Klasa<m_przed,m_po>)'
1> with
1> [
1> m_przed=2,
1> m_po=1
1> ]
1> and
1> [
1> m_przed=2,
1> m_po=3
1> ]

0

Dla klasy Klasa<2,1> masz zdefiniowany tylko operator przypisania biorący Klasa<2,1>, a nie Klasa<2,3>.

0

A mogę prosić o podpowiedź gdzie i w jaki sposób to się zmienia?

0

Możesz zrobić tak:

template <int m_przed, int m_po>
class Klasa
{
	...
	
	template <int m_przed2, int m_po2> 
	Klasa<m_przed, m_po>& operator=(const Klasa<m_przed2, m_po2> &);
};


template <int m_przed, int m_po> 
template <int m_przed2, int m_po2>
Klasa<m_przed, m_po>& Klasa<m_przed, m_po>::operator=(const Klasa<m_przed2, m_po2> &l)
{
	...
	return *this;
}

Tylko problem w tym, że z poziomu takiego operatora nie masz dostępu do prywatnych pól klasy Klasa<m_przed2, m_po2>.

0

Z tym też można sobie radzić. Jeśli składniki prywatne do których chcemy się dobrać nie zależą od parametrów szablonu to wystarczy oddzielić je do nieszablonowej klasy bazowej:

class KlasaBaza {
private:
    string s_przed, s_po;
};

template <int E, int O>
class Klasa : public KlasaBaza {
public:
    template <int E2, int O2>
    Klasa<E, O>& operator=(const Klasa<E2, O2>&);
};
 
template <int E, int O, int E2, int O2>
Klasa<E, O>& Klasa<E, O>::operator=(const Klasa<E2, O2>& other)
{
    ...

Natomiast jeśli interesujące nas składniki są zależne od parametrów szablonu to można sobie zrobić dodatkową funkcję zaprzyjaźnioną z całym szablonem:

template <class T>
class Foo {
private:
    T value; // prywatny składnik którego chcemy użyć

public:
    /* zaprzyjaźniamy się z szablonową funkcją FooAssign, która wykona przypisanie */
    template <class T1, class T2>
    friend Foo<T1> &FooAssign(Foo<T1> &to, const Foo<T2> &from);

    /* operator przypisania tylko "przekierowuje" do FooAssign */
    template <class T2>
    Foo<T> &operator = (const Foo<T2> &other) { return FooAssign(*this, other); }
};

/* implementacja przypisania */
template <class T1, class T2>
static Foo<T1> &FooAssign(Foo<T1> &to, const Foo<T2> &from)
{
    to.value = from.value;
    return to;
}
0

A czy w którymś z opisanych sposobów przeciążając operator= zmieniają się również parametry jego szablonu? Zadanie, które mam wykonać jest dokładnie takie:

Napisz w C++ klasę szablonową, które implementuje liczby stałoprzecinkowe dziesiętne. Parametry szablonu mają być dwa: liczba miejsc przed przecinkiem oraz liczba miejsc po przecinku. Nie będzie oceniana efektywność czasowa implementacji, ale jej wygoda dla programisty i dokładność działania (gdzie to możliwe, nie wolno tracić dokładności!). Mają być przy tym oprogramowanie wszelkie sensowne operatory, wyświetlanie, inicjowanie takich liczb zarówno liczbami całkowitymi jak i zmiennoprzecinkowymi oraz innymi stałoprzecinkowymi (być może innych rozmiarów!), konwersja na zmiennoprzecinkowe. Liczby zbyt małe mają być po cichu przycinane, ale liczby zbyt duże powinny powodować zdefiniowany przez autora wyjątek.

Dla mnie zupełnie nie potrzebne są te parametry szablonu, no ale skoro takie zadanie więc pewnie powinny się zmieniać one również po wykonaniu operatora jeśli trzeba. Chociaż z drugiej strony, nie bardzo rozumiem co znaczy stałoprzecinkowe. Może chodzi o to, że po wykonaniu dowolnej operacji nie trzeba będzie przesuwać przecinka? Wtedy to by uprościło problem, ale z drugiej strony bez sensu byłaby implementacja czegoś takiego.

0

Po pierwsze w ramach ułatwienia nie reprezentowałbym tych liczb jako string, tylko przykładowo int (ograniczenie do 9 cyfr przed/po przecinkiem, int32t gdy używasz innego kompilatora niż wszystkie z którymi się spotkałem), czy long long (18, ale trzeba być bardziej ostrożnym z mnożeniem). W przypadku, gdy większa dokładność jest potrzebna, to użyłbym oddzielnej klasy do obliczeń na liczbach całkowitych z dowolną precyzją. Liczba stałoprzecinkowa są to po prostu liczba całkowita z przesuniętym przecinkiem.

Używając szablonów w ten sposób nie da się dobrze przekazać enkapsulacji przy tych operatorach. Ja bym radził zrobienie tego bez korzystania z innych mechanizmów języku poprzez metody publiczne o nazwach np. __getRepresentation(). Drugim rozwiązaniem może być klasa bazowa z wirtualnymi metodami do pobrania reprezentacji.

0

Oj to zadanie chyba jest jednak trudniejsze niż mi się wydaje. Początkowo miałem pomysł aby działać na stringu jak na tablicy charów i zaimplementować algorytmy pisemnego mnożenia, dodawania itp, ale faktycznie chyba za dużo roboty. I już się zgubiłem w tych liczbach. Stałoprzecinkowe to tak dokładniej jakie, bo w różnych źródłach wyczytuje inaczej, czasem piszą, że to całkowita... ale w sumie w zadaniu nie byłaby stałoprzecinkowa wymieniona obok całkowitej. Zmiennoprzecinkowa to domyślam się, że taka jak float, double. Jaka jest różnica między zmienno a stałoprzecinkową?

0

Liczba zmiennoprzecinkowa to sposób reprezentacji liczb rzeczywistych. Liczba taka składa się z dwóch liczb całkowitych - mantysy (wartość bazowa) oraz wykładnika (pozycja przecinka). Np. liczbę 123,456 można zapisać jako para liczb całkowitych 123456 (mantysa) oraz -3 (wykładnik), bo 123456 * 10 -3 = 123,456 . Albo 1230000 to para liczb 123 oraz 4, bo 123 * 104 = 1230000. We float i double troszkę inny jest ten wzór ale generalnie zasada ta sama.

Liczby stałoprzecinkowe to sposób reprezentacji liczb rzeczywistych przy pomocy jednej liczby całkowitej (mantysy), natomiast wykładnik jest stały i z góry określony. Np. ustalamy, że wykładnik to -4 (czyli pamiętamy zawsze stałą liczbę miejsc po przecinku - 4). Liczba 123.456 w takiej reprezentacji to 1234560, bo 1234560*10^-4 = 123,456 .

Mając powyższe na uwadze, twoja klasa powinna wyglądać trochę inaczej. Konstruktor nie powinien przyjmować długości liczby i pozycji przecinak jako parametry, mają one być z góry określone przez parametry szablonu.

0

hmm czyli to powinno być coś bardziej w tą stronę:?

#include <iostream>
#include <string>

using namespace std;

template <int przed, int po>
class Liczba {
private:
	long long int liczba;
	static const int dlugosc = przed + po;

public:
	Liczba(double param){
		int pom = 1;
		for (int i = 0; i < po; i++){
			pom = pom * 10;
		}
		this->liczba = /*(long long int)*/param * pom;//pozbycie się części ułamkowej
	}

	void Wypisz(){//wypisywanie trochę mało optymalne ale jakoś tam działa		
		string znakiLiczby;
		znakiLiczby.resize(dlugosc+1);

		for (int i = 0; i < dlugosc; i++){
			znakiLiczby[dlugosc-1-i] = liczba%10+48;
			liczba /= 10;
		}
		znakiLiczby.insert(przed, ".");
		cout << znakiLiczby << endl;
	}

};


int main(){
	Liczba <7, 8> a(3311291.41399342);//testowa liczba
	a.Wypisz();

	return 0;
}

ale tutaj też wykonując działania na liczbach będę musiał jakoś pozmieniać parametry szablonu bo miejsce przecinka będzie się raczej zmieniało, a właśnie ze zmianą parametrów szablonu mam aktualnie największy kłopot

1 użytkowników online, w tym zalogowanych: 0, gości: 1