Templates
Variadic Templates
Aliases
01. template <typename T>
02. class Vector {
03. private:
04. // elem points to an array of sz elements of type T
05. T* elem;
06. int sz;
07. public:
08. Vector(int s); // Constructor
09. ~Vector() { delete[] elem; } // Destructor
10.
11. // ... copy and move operations ...
12.
13. T& operator[](int i);
14. const T& operator[](int i) const;
15. int size() const { return sz; }
16. };
01. template <typename T>
02. Vector<T>::Vector(int s)
03. {
04. if (s < 0) throw Negative_size();
05. elem = new T[s];
06. sz = s;
07. }
08.
09. template <typename T>
10. const T& Vector<T>::operator[](int i) const
11. {
12. if (i < 0 || size() <= i)
13. throw out_of_range("Vector::operator[]");
14. return elem[i];
15. }
01. Vector<char> vc(200); // vector of 200 characters
02. Vector<string> vs(17); // vector of 17 strings
03. Vector<list<int>> vli(45); // vector of 45 lists of integers
01. void write(const Vector<string>& vs) // Vector of some strings
02. {
03. for (int i = 0; i!=vs.size(); ++i)
04. cout << vs[i] << 'n';
05. }
01. template<typename T>
02. T* begin(Vector<T>& x)
03. {
04. // pointer to first element
05. return &x[0];
06. }
01. template<typename T>
02. T* end(Vector<T>& x)
03. {
04. // pointer to one-past-last element
05. return x.begin()+x.size();
06. }
01. void f2(const Vector<string>& vs) // Vector of some strings
02. {
03. for (auto& s : vs)
04. cout << s << 'n';
05. }
01. template<typename Container, typename Value>
02. Value sum(const Container& c, Value v)
03. {
04. for (auto x : c)
05. v+=x;
06. return v;
07. }
01. void user(Vector<int>& vi, std::list<double>& ld,
02. std::vector<complex<double>>& vc)
03. {
04. int x = sum(vi,0);// the sum of a vector of ints (add ints)
05. double d = sum(vi,0.0);// the sum of a vector of ints (add doubles)
06. double dd = sum(ld,0.0);// the sum of a list of doubles
07. // the sum of a vector of complex<double> the initial value is {0.0,0.0}
08. auto z = sum(vc,complex<double>());
09. }
01. template<typename T, typename... Tail>
02. void f(T head, Tail... tail)
03. {
04. g(head); // do something to head
05. f(tail...); // try again with tail
06. }
07.
08. void f() { } // do nothing
09.
10. int main()
11. {
12. cout << "first: ";
13. f(1,2.2,"hello");
14. cout << "nsecond: "
15. f(0.2,'c',"yuck!",0,1,2);
16. cout << "n";
17. }
01. template<typename T>
02. void g(T x)
03. {
04. cout << x << " ";
05. }
first: 1 2.2 hello
second: 0.2 c yuck! 0 1 2
01. template<typename T>
02. class Less_than {
03. const T val; // value to compare against
04. public:
05. Less_than(const T& v) :val(v) { }
06. // call operator
07. bool operator()(const T& x) const { return x<val; }
08. };
01. Less_than<int> lti {42}; // lti(i) will compare i to 42 using < (i<42)
02. // lts(s) will compare s to "Backus" using < (s<"Backus")
03. Less_than<string> lts {"Backus"};
04.
05. void fct(int n, const string & s)
06. {
07. bool b1 = lti(n); // true if n<42
08. bool b2 = lts(s); // true if s<"Backus"
09. // ...
10. }
01. template<typename C, typename P>
02. int count(const C& c, P pred)
03. {
04. int cnt = 0;
05. for (const auto& x : c)
06. if (pred(x))
07. ++cnt;
08. return cnt;
09. }
01. void f(const Vector<int>& vec, const list<string>& lst, int x
02. , const string& s)
03. {
04. cout << "number of values less than " << x
05. << ": " << count(vec,Less_than<int>{x}) << 'n';
06. cout << "number of values less than " << s
07. << ": " << count(lst,[&](const string& a){ return a<s; })
08. << 'n';
09. }
01. typedef unsigned int size_t;
02.
03. using size_t = unsigned int;
04.
05. template<typename T>
06. class Vector {
07. public:
08. using value_type = T;
09. // ...
10. };
01. template<typename C>
02. using Element_type = typename C::value_type;
03.
04. template<typename Container>
05. void algo(Container& c)
06. {
07. Vector<Element_type<Container>> vec; // keep results here
08. // ...
09. }
01. template<typename Key, typename Value>
02. class Map {
03. // ...
04. };
05.
06. template<typename Value>
07. using String_map = Map<string,Value>;
08.
09. String_map<int> m; // m is a Map<string,int>
11. template

11. template

  • 2.
  • 3.
    01. template <typenameT> 02. class Vector { 03. private: 04. // elem points to an array of sz elements of type T 05. T* elem; 06. int sz; 07. public: 08. Vector(int s); // Constructor 09. ~Vector() { delete[] elem; } // Destructor 10. 11. // ... copy and move operations ... 12. 13. T& operator[](int i); 14. const T& operator[](int i) const; 15. int size() const { return sz; } 16. };
  • 4.
    01. template <typenameT> 02. Vector<T>::Vector(int s) 03. { 04. if (s < 0) throw Negative_size(); 05. elem = new T[s]; 06. sz = s; 07. } 08. 09. template <typename T> 10. const T& Vector<T>::operator[](int i) const 11. { 12. if (i < 0 || size() <= i) 13. throw out_of_range("Vector::operator[]"); 14. return elem[i]; 15. }
  • 5.
    01. Vector<char> vc(200);// vector of 200 characters 02. Vector<string> vs(17); // vector of 17 strings 03. Vector<list<int>> vli(45); // vector of 45 lists of integers 01. void write(const Vector<string>& vs) // Vector of some strings 02. { 03. for (int i = 0; i!=vs.size(); ++i) 04. cout << vs[i] << 'n'; 05. }
  • 6.
    01. template<typename T> 02.T* begin(Vector<T>& x) 03. { 04. // pointer to first element 05. return &x[0]; 06. } 01. template<typename T> 02. T* end(Vector<T>& x) 03. { 04. // pointer to one-past-last element 05. return x.begin()+x.size(); 06. } 01. void f2(const Vector<string>& vs) // Vector of some strings 02. { 03. for (auto& s : vs) 04. cout << s << 'n'; 05. }
  • 7.
    01. template<typename Container,typename Value> 02. Value sum(const Container& c, Value v) 03. { 04. for (auto x : c) 05. v+=x; 06. return v; 07. } 01. void user(Vector<int>& vi, std::list<double>& ld, 02. std::vector<complex<double>>& vc) 03. { 04. int x = sum(vi,0);// the sum of a vector of ints (add ints) 05. double d = sum(vi,0.0);// the sum of a vector of ints (add doubles) 06. double dd = sum(ld,0.0);// the sum of a list of doubles 07. // the sum of a vector of complex<double> the initial value is {0.0,0.0} 08. auto z = sum(vc,complex<double>()); 09. }
  • 8.
    01. template<typename T,typename... Tail> 02. void f(T head, Tail... tail) 03. { 04. g(head); // do something to head 05. f(tail...); // try again with tail 06. } 07. 08. void f() { } // do nothing 09. 10. int main() 11. { 12. cout << "first: "; 13. f(1,2.2,"hello"); 14. cout << "nsecond: " 15. f(0.2,'c',"yuck!",0,1,2); 16. cout << "n"; 17. }
  • 9.
    01. template<typename T> 02.void g(T x) 03. { 04. cout << x << " "; 05. } first: 1 2.2 hello second: 0.2 c yuck! 0 1 2
  • 10.
    01. template<typename T> 02.class Less_than { 03. const T val; // value to compare against 04. public: 05. Less_than(const T& v) :val(v) { } 06. // call operator 07. bool operator()(const T& x) const { return x<val; } 08. }; 01. Less_than<int> lti {42}; // lti(i) will compare i to 42 using < (i<42) 02. // lts(s) will compare s to "Backus" using < (s<"Backus") 03. Less_than<string> lts {"Backus"}; 04. 05. void fct(int n, const string & s) 06. { 07. bool b1 = lti(n); // true if n<42 08. bool b2 = lts(s); // true if s<"Backus" 09. // ... 10. }
  • 11.
    01. template<typename C,typename P> 02. int count(const C& c, P pred) 03. { 04. int cnt = 0; 05. for (const auto& x : c) 06. if (pred(x)) 07. ++cnt; 08. return cnt; 09. } 01. void f(const Vector<int>& vec, const list<string>& lst, int x 02. , const string& s) 03. { 04. cout << "number of values less than " << x 05. << ": " << count(vec,Less_than<int>{x}) << 'n'; 06. cout << "number of values less than " << s 07. << ": " << count(lst,[&](const string& a){ return a<s; }) 08. << 'n'; 09. }
  • 12.
    01. typedef unsignedint size_t; 02. 03. using size_t = unsigned int; 04. 05. template<typename T> 06. class Vector { 07. public: 08. using value_type = T; 09. // ... 10. };
  • 13.
    01. template<typename C> 02.using Element_type = typename C::value_type; 03. 04. template<typename Container> 05. void algo(Container& c) 06. { 07. Vector<Element_type<Container>> vec; // keep results here 08. // ... 09. } 01. template<typename Key, typename Value> 02. class Map { 03. // ... 04. }; 05. 06. template<typename Value> 07. using String_map = Map<string,Value>; 08. 09. String_map<int> m; // m is a Map<string,int>

Editor's Notes

  • #3 اول template رو توضیح خواهم داد بعد variadic templateها رو خواهم گفت و در آخر در مورد نام مستعار صحبت می کنم.
  • #4 معمولا کسی که از Vector استفاده می کنه همشه نمی خواد که double توش نگه داره. مفهوم vector یک مفهوم عامه و مستقل از اینه که توش چه چیزی نگه داری بشه. پس باید یه راهی برای مستقل کردن type المان هایی که داخل یه وکتور هستند پیدا کنیم. با استفاده از template ما می تونیم یک کلاس و یا یک function رو به صورت پارامتری در بیاریم که با یه رنج وسیعتری از typeها کار کنه. برای اینکه ما مفهوم vectorیی از doubleها رو تعمیم بدیم به vectorیی از هر چیزی؛ از template استفاده می کنیم. بجای اینکه از double استفاده کنیم از یه parameter استفاده می کنیم. این template<typename T> به معنی اینه که T رو به عنوان پارامتر استفاده می خوایم بکنیم. و معادل به ازای همه یtypeهای از نوع T هستش. حالا بجای اینکه بنویسیم *double می نویسیم *T و بعد از این کامپایلر هر وقت که از این template استفاده کنیم بجای T اون Typeیی رو که ما میخوایم جایگذاری میکنه.
  • #5 Define کردن member functionها هم به این صورته. یه template <typename T> می نویسیم بعد اسم کلاس رو به این صورت می نویسیم و بعد :: و اسم اون member functionیی که می خوایم define کنیم رو می نویسیم. بقیه موارد دقیقا مثل قبله و به پیاده سازی ها به همون صورته که با double دیده بودیم. فقط بجای double از یه typeی پارامتری به نام T استفاده کردیم.
  • #6 حالا ما میتونیم با تعریف کردن vector به صورت template یه سری vector با typeهای مختلف بسازیم. برای تعریف کردن یه وکتور که یه آرایه از char ها رو نگه داره مثل خط اول باید بنویسیم و به همین صورت وکتوری از رشته ها و یه وکتور از یه لیستی از اعداد صحیح هم به ترتیب تعریف شدن. الان می تونیم وکتوری از هر typeیی رو تعریف کنیم و کامپایلر با توجه به typeیی که جلوی وکتور می نویسیم بجای پارامتر T جایگذاری میکنه. و به این صورت هم ازشون استفاده کنیم. مثلا می تونیم یه وکتور از رشته ها رو به عنوان ورودی بگیریم و مقادیر رشته ها رو روی صفحه چاپ کنیم.
  • #7 برای اینکه containerهایی که به صورت templateیی تعریف می کنیم با range for هم سازگار باشه و بشه روی اونا پیمایش کرد باید داخل کلاس وکتور 2 تا function به نامهای begin و end هم تعریف کنیم. Templateها زمان کامپایل ساخته می شن و در زمان کامپایل بجای پارامترها؛ Typeهای مناسب جایگزین میشن. پس templateها زمان اجرا هیچ سرباری از بابت عوض کردن typeها ندارند همه چیز زمان کامپایل صورت می گیره. حالا میشه با range for هر وکتوری با هر type پارامتری رو پیمایش کرد.
  • #8 Templateها استفاده های خیلی بیشتری دارند و کاربرد اونا فقط محدود به پارامتری کردن typeی المانهای containerها نیست. از templateها هم برای پارامتری کردن typeها و هم برای پارامتری کردن الگوریتم ها استفاده میشه. به عنوان مثال ما می تونیم تابعی تعریف کنیم که حاصل جمع تمام المانهای هر containerیی از هر typeیی رو محاسبه کنه و برگردونه. آرگمان Valueی template و ورودی v برای این هستند که مقدار اولیه ی حاصل جمع رو به تابع sum بدیم. با استفاده از sum میتونیم حاصل جمع هر containerیی رو حساب کنیم فرقی نمیکنه که int باشه یا double باشه یا حتی complex باشه. نکته ای که هست اینه که کامپایلر خودش به صورت هوشمند typeهایی که باید بجای پارامتر های template بذاره رو تشخیص میده و اونا رو جایگذاری میکنه و لازم نیست که صراحتا بگیم که چه typeهایی استفاده می کنیم. نکته ی آخر هم اینکه تابع sum یه نسخه ی ساده شده ی یکی از تابع های کتابخونه ی استاندارد به نام accumulate هست.
  • #9 Templateهایی که میتونن تعداد دلخواهی پارامتر داشته باشن رو variadic template می گیم. به این صورت تعریف می شن آرگمان دوم بعد از typename یه ... قرار میگیره و اسم اون مشخص کننده ی یه لیست از آرگمانهاست. نکته ی اصلی در پیاده سازی variadic templateها اینه که ما یه لیست از آرگمان ها به نام tail رو بهش پاس میدیم و اولین آرگمان رو به نام head از بقیه جداسازی می کنیم و بعد روی اولین آرگمان هر کاری که لازم هست رو انجام میدیم و بعد به صورت recursive یا بازگشتی دوباره خودش رو با بقیه ی آرگمانها فرا خوانی می کنیم. یه تابع خالی هم می نویسم که هیچ آرگمانی نداره و هیچ کاری انجام نمیده و زمانی فراخوانی میشه که هیچ آرگمانی در ورودی تابع باقی نمونده باشه. حالا می تونیم به تابع هر تعداد ورودی با هر typeیی رو بدیم و ازش استفاده کنیم.
  • #10 برای اینکه تمام ورودی ها تابع رو چاپ کنیم باید تابع g رو به این صورت تعریف کنیم داخلش خیلی ساده از cout استفاده می کنیم و آرگمان ورودی رو چاپ می کنیم. خروجی تابع mainما به این صورت خواهد شد. تابع f یه چیزی مشابه printf هست که می تونه به هر تعداد ورودی رو دریافت کنه و اونا رو چاپ کنه.
  • #11 یکی از کاربردهای خیلی خوب template تعریف کردن function objectهاست. گاهی function object رو functor هم میگن. Function object ها یه objectهایی هستند که میشه با اونا مثل function رفتار کرد. اپراتور () یا اپراتور function call باعث میشه که ما بتونیم یه object رو مثل یه function بکار ببریم. نحوه ی استفاده به این صورته که ما میتونیم از function object ی Less_than هرچندتا متغییر که بخوایم با پارامتر های مختلف ایجاد کنیم. مثلا برای مقایسه کردن typeهای int با عدد 42 و یا برای مقایسه کردن رشته ها با یه backus از function object ی Less_than متغییر یا Object ایجاد کنیم. حالا میشه مثل یه function از اون Object استفاده کرد. مثلا با lti میتونیم چک کنیم که عدد n از 42 کوچکتر هست یا نه. یا اینکه رشته ی s از backus کوچکتر هست یا نه.
  • #12 کاربرد اصلی function objectها توی الگوریتم هاست و به عنوان پارامتر ورودی به توابع الگوریتم بکار میرن. مثلا فرض کنید یه template function به نام count نوشتیم که یه predicate به نام pred میگیره و هر وقت که به صورت function با پارامتر x صدا زده شد و true بر گردوند ما یکی مقدار cnt رو اضافه میکنیم. درواقع تعداد رخدادن یه چیزی رو با استفاده از pred میشماریم و اونو برمی گردونیم. Predicate چیزیه که ما میتونیم اونو با function call اجرا کنیم و به ما مقدار true یا false بر میگردونه. حالا میتونیم به این صورت ازش استفاده کنیم. یه وکتور از integerها بهش میدیم و با less_than تعداد المانهایی که از x کوچکتر هستند رو میشماریم. اما نوشتن function object برای هر کاری یه کم سخته. C++11 یه امکان جدیدی به ما میده که میتونیم lambda expression بنویسم که مشابه function object کار میکنه ولی inline هست. اول یه براکت باز و بسته مینوسیم که بهش میگن capture list و مشخص کننده اینه که متغییر های Local که داخلش استفاده شده به صورت reference بهش داده میشن. اگه بجای & از = استفاده کنید به کامپایلر میگید که به صورت value مغییر ها رو کپی کنه. و بعد با پرانتز باز و بسته پارامتر های ورودی رو بهش معرفی میکنید و بعد با آکولاد باز و بسته بدنه ی تابع رو مینویسید. Lambda زمانی استفاده میشه که شما می خواید یه anonymous function بنویسید و سریع کارتو رو راه بنداید خود کامپایلر میاد معادل function object اون رو برای شما میسازه.
  • #13 خیلی وقتا خیلی خوبه که یک نام مستعار برای typeها بسازیم و از اون استفاده کنیم. توی C++ نام مستعار درست کردن مخصوصا برای templateها خیلی رایجه. ما در C از typedef برای این کار استفاده می کردیم. الان در C++11 میتونیم از using برای این نام مستعار درست کردن استفاده کنیم. Size_t یه type هست که implementation-dependent هست یعنی اینکه توی هر سیستمی و یا توی هر کامپایلری ممکنه فرق داشته باشه. روی یه سیستم ممکنه unsigned int باشه و توی یه سیستم دیگه ممکنه unsigned long باشه. برای اینکه برنامه ها Portable باشند برای size_t با استفاده از using یا typedef نام مستعار می سازند. همینطور برای پارامتری کردن typeها هم از نام مستعار استفاده می کنن و آرگمان های template رو با نام مستعار مشخص می کنن. به عنوان مثال تمام container های کتابخونه ی استاندارد یه alias به نام value_type رو تعریف می کنن.
  • #14 تعریف کردن alias برای value_type به ما اجازه میده که کدهایی مثل این بنویسیم. این کد یه container رو میگیره و با استفاده از value_type اون یه وکتور میسازه که بتونه نتایج محاسباتی که قرار انجام بشه رو توی اون ذخیره کنه. از alias برای تعریف کردن templateهای جدید میتونیم استفاده کنیم و بعضی از آرگمان های template رو bind کنیم. یعنی که یه آرگمان رو Fix میکنیم و دیگه لازم نیست که دائم اولین آرگمان رو بنویسیم و فقط دومین آرگمان رو مشخص می کنیم. مثلا یه map تعریف می کنیم که آرگمان اول اون همیشه رشته هست.
  • #15 در انتهای این ویدیو فصل سوم کتاب تموم شد. توی این فصل کلا با مکانیزم های abstraction آشنا شدیم و هر کدوم رو بررسی کردیم. مفاهیم Copy و Move رو بحث کردیم و موارد کار برد templateها رو هم دیدیم.