W lekcji 11.7 -- Szablon funkcji instancja, omawialiśmy, w jaki sposób kompilator będzie używać szablonów funkcji do tworzenia instancji funkcji, które są następnie kompilowane. Zauważyliśmy również, że te funkcje mogą się nie skompilować, jeśli kod w szablonie funkcji spróbuje wykonać jakąś operację, której rzeczywisty typ nie obsługuje (np. dodanie wartości całkowitej 1 do std::string).
W tej lekcji przyjrzymy się kilku przykładom, w których nasze instancje funkcji nie zostaną skompilowane, ponieważ nasze rzeczywiste typy klas nie obsługują tych operatorów, i pokażemy, jak możemy zdefiniować te operatory, aby utworzone instancje funkcji zostały następnie skompilowane.
Operatory, wywołania funkcji i szablony funkcji
Najpierw utwórzmy prostą klasę:
class Cents
{
private:
int m_cents{};
public:
Cents(int cents)
: m_cents { cents }
{
}
friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
{
ostr << c.m_cents;
return ostr;
}
};i zdefiniuj max szablon:
template <typename T>
const T& max(const T& x, const T& y)
{
return (x < y) ? y : x;
}Teraz zobaczmy, co się stanie, gdy spróbujemy wywołać max() z obiektem typu Cents:
#include <iostream>
class Cents
{
private:
int m_cents{};
public:
Cents(int cents)
: m_cents { cents }
{
}
friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
{
ostr << c.m_cents;
return ostr;
}
};
template <typename T>
const T& max(const T& x, const T& y)
{
return (x < y) ? y : x;
}
int main()
{
Cents nickel{ 5 };
Cents dime{ 10 };
Cents bigger { max(nickel, dime) };
std::cout << bigger << " is bigger\n";
return 0;
}C++ utworzy instancję szablonu dla max(), która wygląda tak:
template <>
const Cents& max(const Cents& x, const Cents& y)
{
return (x < y) ? y : x;
}A następnie spróbuje skompilować tę funkcję. Widzisz problem tutaj w C++? nie ma pojęcia, jak ocenić x < y gdy x i y są typu Cents! W rezultacie spowoduje to błąd kompilacji.
Aby obejść ten problem, po prostu przeciąż operator< dowolną klasę, której chcemy użyć max przez:
#include <iostream>
class Cents
{
private:
int m_cents {};
public:
Cents(int cents)
: m_cents { cents }
{
}
friend bool operator< (const Cents& c1, const Cents& c2)
{
return (c1.m_cents < c2.m_cents);
}
friend std::ostream& operator<< (std::ostream& ostr, const Cents& c)
{
ostr << c.m_cents;
return ostr;
}
};
template <typename T>
const T& max(const T& x, const T& y)
{
return (x < y) ? y : x;
}
int main()
{
Cents nickel{ 5 };
Cents dime { 10 };
Cents bigger { max(nickel, dime) };
std::cout << bigger << " is bigger\n";
return 0;
}Działa to zgodnie z oczekiwaniami i wypisuje:
10 is bigger
Kolejny przykład
Zróbmy jeszcze jeden przykład szablonu funkcji, który nie działa z powodu braku przeciążonych operatorów.
Następujący szablon funkcji obliczy średnią z liczby obiektów w array:
#include <iostream>
template <typename T>
T average(const T* myArray, int numValues)
{
T sum { 0 };
for (int count { 0 }; count < numValues; ++count)
sum += myArray[count];
sum /= numValues;
return sum;
}
int main()
{
int intArray[] { 5, 3, 2, 1, 4 };
std::cout << average(intArray, 5) << '\n';
double doubleArray[] { 3.12, 3.45, 9.23, 6.34 };
std::cout << average(doubleArray, 4) << '\n';
return 0;
}To generuje wartości:
3 5.535
Jak widać, działa świetnie w przypadku typów wbudowanych!
Zobaczmy teraz, co się stanie, gdy wywołamy tę funkcję na naszym Cents klasę:
#include <iostream>
template <typename T>
T average(const T* myArray, int numValues)
{
T sum { 0 };
for (int count { 0 }; count < numValues; ++count)
sum += myArray[count];
sum /= numValues;
return sum;
}
class Cents
{
private:
int m_cents {};
public:
Cents(int cents)
: m_cents { cents }
{
}
};
int main()
{
Cents centsArray[] { Cents { 5 }, Cents { 10 }, Cents { 15 }, Cents { 14 } };
std::cout << average(centsArray, 4) << '\n';
return 0;
}Kompilator wpada w szał i generuje mnóstwo komunikatów o błędach! Pierwszy komunikat o błędzie będzie mniej więcej taki:
error C2679: binary << : no operator found which takes a right-hand operand of type Cents (or there is no acceptable conversion)
Pamiętaj ten average() zwraca a Cents obiekt i próbujemy przesłać strumieniowo ten obiekt do std::cout za pomocą operator<<. Jednak nie zdefiniowaliśmy jeszcze operator<< dla naszej Cents klasy. Zróbmy to:
#include <iostream>
template <typename T>
T average(const T* myArray, int numValues)
{
T sum { 0 };
for (int count { 0 }; count < numValues; ++count)
sum += myArray[count];
sum /= numValues;
return sum;
}
class Cents
{
private:
int m_cents {};
public:
Cents(int cents)
: m_cents { cents }
{
}
friend std::ostream& operator<< (std::ostream& out, const Cents& cents)
{
out << cents.m_cents << " cents ";
return out;
}
};
int main()
{
Cents centsArray[] { Cents { 5 }, Cents { 10 }, Cents { 15 }, Cents { 14 } };
std::cout << average(centsArray, 4) << '\n';
return 0;
}Jeśli skompilujemy ponownie, otrzymamy kolejny błąd:
error C2676: binary += : Cents does not define this operator or a conversion to a type acceptable to the predefined operator
Ten błąd jest w rzeczywistości spowodowany instancją szablonu funkcji utworzoną podczas wywoływania average(const Cents*, int). Pamiętaj o tym, gdy wywołujemy a templated, kompilator „wzorcuje” kopię funkcji, w której parametry typu szablonu (typy zastępcze) zostały zastąpione rzeczywistymi typami w wywołaniu funkcji. Oto instancja szablonu funkcji dla obiektu average() gdy T jest Cents :
template <>
Cents average(const Cents* myArray, int numValues)
{
Cents sum { 0 };
for (int count { 0 }; count < numValues; ++count)
sum += myArray[count];
sum /= numValues;
return sum;
}Powodem otrzymania komunikatu o błędzie jest następujący wiersz:
sum += myArray[count];W tym przypadku sum jest Cents obiekt, ale nie zdefiniowaliśmy operator+= for Cents obiektów. Będziemy musieli to zrobić! zdefiniuj tę funkcję, aby average() móc pracować z Cents. Patrząc w przyszłość, widzimy, że average() również używa operator/=, więc przejdziemy dalej i zdefiniujemy również to:
#include <iostream>
template <typename T>
T average(const T* myArray, int numValues)
{
T sum { 0 };
for (int count { 0 }; count < numValues; ++count)
sum += myArray[count];
sum /= numValues;
return sum;
}
class Cents
{
private:
int m_cents {};
public:
Cents(int cents)
: m_cents { cents }
{
}
friend std::ostream& operator<< (std::ostream& out, const Cents& cents)
{
out << cents.m_cents << " cents ";
return out;
}
Cents& operator+= (const Cents ¢s)
{
m_cents += cents.m_cents;
return *this;
}
Cents& operator/= (int x)
{
m_cents /= x;
return *this;
}
};
int main()
{
Cents centsArray[] { Cents { 5 }, Cents { 10 }, Cents { 15 }, Cents { 14 } };
std::cout << average(centsArray, 4) << '\n';
return 0;
}W końcu nasz kod się skompiluje i uruchomi! Oto wynik:
11 cents
Zauważ, że nie musieliśmy zmodyfikować average() aby działał z obiektami typu Cents Musieliśmy po prostu zdefiniować operatory użyte do implementacji average() dla klasy Cents , a kompilator zajął się resztą!

