ในขณะที่เรียนรู้ C++ คุณได้พบกับเทมเพลตที่เป็นวิธีที่ยอดเยี่ยมในการสร้างฟังก์ชันและคลาสทั่วไป แต่มันคือ C++ มาเลย คุณสามารถทำได้มากกว่านั้น เทมเพลต C++ เป็นเพื่อนของฉัน อย่าหยุดอยู่เพียงคลาสและฟังก์ชันเท่านั้น มันเป็นอีกโลกหนึ่ง โลกแห่งการเขียนโปรแกรมเมตาเทมเพลต แต่อย่างไรก็ตาม คุณเพียงต้องการใช้เทมเพลต C++ ให้เก่ง ไม่ต้องกังวล ฉันจะเสนอปัญหาเทมเพลต C++ ที่เหมาะสมให้กับคุณ ซึ่งเรียกว่า "Peano Numbers"
คุณสามารถแก้ปัญหาได้ที่นี่ "หมายเลข Peano"
ก่อนอื่น อย่า google อย่า stackoverflow อย่า chatgpt-it อย่าเขียนโค้ด llama ยังไงก็เข้าใจ
ลองด้วยตัวเอง เสียเวลาจนกว่าคุณจะสร้างมันขึ้นมา หรืออับอายแล้วตรวจสอบวิธีแก้ปัญหา
สารละลาย
คุณยอมแพ้เหรอ? 😒
คิดวิธีแก้ปัญหาไม่ได้ใช่ไหม? ยอดเยี่ยม
ไม่ต้องละอายใจที่จะลอง ฉันก็ประสบปัญหานี้เหมือนกัน
ดังนั้นเราจึงมีจุดเริ่มต้น
struct Peano {}; struct Zero: Peano {}; template<class T> struct Succ: Peano {};
ซึ่งคล้ายกับ 0 และตัวเลขที่มากกว่าหรือเท่ากับ 1 เพื่อให้เราสามารถแทนตัวเลขใดๆ ได้
เรามาแจกแจงรายละเอียดนี้และมุ่งเน้นไปที่ส่วนเล็กๆ เนื่องจากฉันใช้แบบเทมเพลต ก่อนอื่นฉันต้องมีความคิดว่าจะเกิดอะไรขึ้น และฉันจะแก้ไขปัญหาอย่างไร
การดำเนินการเพิ่ม
template <class T1, class T2> struct Add {};
แล้วตอนนี้ฉันจะทำสิ่งนี้ได้อย่างไร? ฉันไม่สามารถใช้ตัวดำเนินการหรือจำนวนเต็มในตัวได้
ฉันต้องพึ่งพาเทมเพลตที่ C++ นำเสนอ
เอาล่ะ คุณ เทมเพลต C++ :
- คุณสามารถทำความเชี่ยวชาญด้านเทมเพลตได้
- คุณสามารถทำตัวเหมือนการจับคู่รูปแบบแต่สำหรับประเภท
- คุณสามารถทำการเรียกซ้ำได้
เนื่องจากฉันไม่สามารถใช้โอเปอเรเตอร์ได้ ฉันจึงมีข้อจำกัดอย่างมาก ดังนั้นฉันจึงต้องใช้ความคิดสร้างสรรค์ในเรื่องนี้ ฉันรู้ว่าฉันต้องมีการเรียกซ้ำเพื่อทำซ้ำการดำเนินการและความเชี่ยวชาญพิเศษของเทมเพลต เพื่อสร้างข้อยกเว้นว่าคอมไพลเลอร์ควรทำงานอย่างไรเมื่อเป็นไปตามเกณฑ์ที่กำหนด
มาดูกันว่าฉันจะใช้ความเชี่ยวชาญพิเศษของเทมเพลตได้อย่างไร ตามปัญหา 1 และ 0 จะแสดงเป็น 'Succ‹Zero›' และ 'ศูนย์' เราสามารถรวมได้มากเท่ากับ 'Succ' เท่าที่เราสามารถทำได้เพื่อแสดงตัวเลข นั่นคือการบวกพื้นฐานด้วย 1 แล้วการลบด้วย 1 ล่ะ?
ฉันเดาว่าฉันสามารถทำตรงกันข้ามและแกะหมายเลขภายในได้
template <class T1, class T2> struct Add {}; template <class T1, class T2> struct Add<T1, Succ<T2>> {};
ตอนนี้เรามี T1 ซึ่งเป็นตัวเลข และ T2 ซึ่งเป็นตัวเลข-1 อีกตัว
ฉันเห็นบางอย่างอยู่ตรงนี้ เมื่อคุณลองคิดดู ฉันสามารถแสดงการบวกเป็นการลบ 1 จากตัวถูกดำเนินการทางขวา และเพิ่ม 1 ไปทางตัวถูกดำเนินการทางซ้ายได้
จะเป็นอย่างไรถ้าฉันบวก 1 และลบ 1 ต่อไปจนกระทั่งตัวถูกดำเนินการที่ถูกต้องคือ 'ศูนย์' ฉันคิดว่าฉันเข้าใจแล้ว เมื่อเทมเพลต C++ เป็นแบบเรียกซ้ำ ฉันสามารถเรียกการดำเนินการเดิมซ้ำแล้วซ้ำอีกโดยทำหน้าที่เป็นลูปได้
เยี่ยมมาก
template <class T1, class T2> struct Add {}; template <class T1, class T2> struct Add<T1, Succ<T2>> { using type = typename Add<Succ<T1>, T2>::type; };
การเรียกซ้ำแบบ Woopse โดยไม่มีจุดหยุด ใช่แล้ว… มาเพิ่มจุดที่มันควรหยุดกันดีกว่า
เนื่องจากเราไม่ได้จัดการกับจำนวนลบ เราจึงสามารถหยุดได้เมื่อตัวถูกดำเนินการตัวที่สองเป็นศูนย์
template <class T1, class T2> struct Add {}; template <class T1, class T2> struct Add<T1, Succ<T2>> { using type = typename Add<Succ<T1>, T2>::type; }; template <class T1> struct Add<T1, Zero> { using type = T1; };
และตอนนี้ถึงเวลาสำหรับการทดสอบจริง
C++ มีชุดยูทิลิตี้สำหรับการจัดการกับเทมเพลตที่กำหนดไว้ในส่วนหัว '‹type_traits›'
ในส่วนหัว type_traits ฉันต้องการ “std::is_same_v‹T1, T2›” ซึ่งตรวจสอบว่า T1 และ T2 เป็นประเภทเดียวกันหรือไม่และส่งคืนค่าจริงหรือเท็จ
#include <type_traits> #include <iostream> template <class T1, class T2> struct Add {}; template <class T1, class T2> struct Add<T1, Succ<T2>> { using type = typename Add<Succ<T1>, T2>::type; }; template <class T1> struct Add<T1, Zero> { using type = T1; }; int main() { using One = Succ<Zero>; using Two = Succ<One>; using Three = Succ<Two>; std::cout << std::is_same_v<typename Add<One, One>::type, Two> << '\n'; // compare two types std::cout << std::is_same_v<typename Add<Two, One>::type, Three> << '\n'; std::cout << std::is_same_v<typename Add<One, Three>::type, Two> << '\n'; }
การคอมไพล์และรันโค้ดต่อไปนี้ทำให้เรา:
1 1 1
1 สำหรับ true และ 0 สำหรับ false (เราสามารถใช้ std::boolalpha เพื่อส่งออกค่าบูลีนแทน)
จนถึงตอนนี้ ! เจ๋งทุกอย่างใช้งานได้ ตอนนี้ด้วยความคิดนั้น ฉันสามารถทำการคูณและลบเพื่อทำงาน และการหารเป็นเพียงลำดับการลบเลขคี่และเลขคู่ ตัดสินและไปไกลถึงการดำเนินการทางคณิตศาสตร์ทั้งหมด แคลคูลัส พีชคณิต กลศาสตร์ควอนตัมด้วย
การดำเนินการลบ
เอาล่ะ มาลองลบกัน ปัญหาแนะนำให้เราตรวจสอบข้อผิดพลาดในกรณีที่ผลลัพธ์เป็นลบ
template <class T1, class T2> struct Sub {};
นอกจากนี้ ฉันยังคงพันตัวถูกดำเนินการทางซ้ายใน Succ N ครั้ง จนกระทั่งตัวถูกดำเนินการที่สองถึงศูนย์
สำหรับการลบ ฉันสามารถทำได้เช่นเดียวกัน แต่ด้วยการแกะตัวถูกดำเนินการทางซ้าย N ครั้ง จนกระทั่งตัวถูกดำเนินการที่สองถึงศูนย์
template <class T1, class T2> struct Sub {}; template <class T1, class T2> struct Sub<Succ<T1>, Succ<T2>> { using type = typename Sub<T1, T2>::type; }; template <class T1> struct Sub<T1, Zero> { using type = T1; };
แต่ฉันต้องจัดการกรณีที่ตัวถูกดำเนินการตัวแรกถึงศูนย์ก่อนตัวถูก โดยการเพิ่มประเภทข้อผิดพลาดและตั้งค่าเป็นลบเมื่อตัวถูกดำเนินการที่ 1 เป็นศูนย์และ NoError เมื่อการดำเนินการสามารถเสร็จสิ้นโดยไม่มีข้อผิดพลาด
template <class T1, class T2> struct Sub {}; template <class T1, class T2> struct Sub<Succ<T1>, Succ<T2>>: Sub<T1, T2> {}; template <class T1> struct Sub<T1, Zero> { using error = NoError; using type = T1; }; template <class T2> struct Sub<Zero, Succ<T2>> { using error = Negative; };
ครั้งนี้ฉันใช้คลาสย่อยและไม่ได้ระบุ `type` อย่างชัดเจนเพราะฉันไม่ต้องการระบุค่าเมื่อมีข้อผิดพลาดเชิงลบ
ตอนนี้เรามาดูกันว่าจะแสดงผลลัพธ์ที่ถูกต้องหรือไม่
#include <ios> #include <type_traits> template <class T1, class T2> struct Sub {}; template <class T1, class T2> struct Sub<Succ<T1>, Succ<T2>>: Sub<T1, T2> {}; template <class T1> struct Sub<T1, Zero> { using error = NoError; using type = T1; }; template <class T2> struct Sub<Zero, Succ<T2>> { using error = Negative; }; int main() { using One = Succ<Zero>; using Two = Succ<One>; using Three = Succ<Two>; using Four = Succ<Three>; std::cout << std::boolalpha; std::cout << std::is_same_v<typename Sub<One, One>::type, Zero> << '\n'; std::cout << std::is_same_v<typename Sub<Two, One>::type, One> << '\n'; std::cout << std::is_same_v<typename Sub<Four, Two>::type, Two> << '\n'; std::cout << std::is_same_v<Sub<Zero, Two>::error, Negative> << '\n'; } true true true true
และมันได้ผล!
การดำเนินการคูณ
ทีนี้สำหรับการคูณบิ๊กบอย คุณก็รู้แล้วว่ามาเขียนการคูณเป็นการบวกกันดีกว่า
4 * 3 คือ 4 + 4 + 4
5 * 7 คือ 5 + 5 + 5 + 5 + 5 + 5 + 5
2 * 4 คือ 2 + 2 + 2 + 2
ฉันเห็นรูปแบบตรงนี้ เราเก็บตัวถูกดำเนินการตัวแรกให้คงที่และเพิ่มเข้าไปในตัวมันเอง N ครั้ง (N = ค่าของตัวถูกดำเนินการตัวที่สอง) ดูเหมือนว่าการบวกและการลบ
มาเขียนมันในฟังก์ชันกันดีกว่า
int multiply(int a, int b) { int c = a; for(int i = 1; i < b; ++i) { c += a; } return c; }
ใช้งานได้ดีแต่เราไม่สามารถมี for loops ในเทมเพลตได้ ทั้งหมดที่เรามีคือการเรียกซ้ำ ลองใช้การเรียกซ้ำแทน
int multiply(int a, int b) { if(b == 1) return a; return a + multiply(a, b - 1); } // multiply(4, 3) => 4 + multiply(4, 2) => 4 + 4 + multiply(4, 1) => 4 + 4 + 4 = 12
อย่างไรก็ตาม การใช้งานนี้อาจทำการคำนวณมากกว่าที่จำเป็นหาก b หรือ a เป็น 0
int multiply(int a, int b) { if(b == 1) return a; if(b == 0) return 0; return a + multiply(a, b - 1); } // or even better int multiply(int a, int b) { if(b == 0 || a == 0) return 0; return a + multiply(a, b - 1); }
การแปลสิ่งนี้ในเทมเพลต C ++ ให้
#include<type_traits> template <class T1, class T2> struct Mul {}; template <class T1, class T2> struct Mul<T1, Succ<T2>> { using type = std::conditional_t<std::is_same_v<T1, Zero>, Zero, typename Add<T1, typename Mul<T1, T2>::type>::type>; // std::condtitional_t here works like if(a == 0) return 0 }; // if (b == 0) return 0 template <class T1> struct Mul<T1, Zero> { using type = Zero; };
การทดสอบ
#include<type_traits> #include<ios> template <class T1, class T2> struct Mul {}; template <class T1, class T2> struct Mul<T1, Succ<T2>> { using type = std::conditional_t<std::is_same_v<T1, Zero>, Zero, typename Add<T1, typename Mul<T1, T2>::type>::type>; // std::condtitional_t here works like if(a == 0) return 0 }; // if (b == 0) return 0 template <class T1> struct Mul<T1, Zero> { using type = Zero; }; int main() { using One = Succ<Zero>; using Two = Succ<One>; using Three = Succ<Two>; using Four = Succ<Three>; std::cout << std::boolalpha; std::cout << std::is_same_v<typename Mul<One, One>::type, One> << '\n'; std::cout << std::is_same_v<typename Mul<Two, One>::type, Two> << '\n'; std::cout << std::is_same_v<typename Mul<Four, Zero>::type, Zero> << '\n'; std::cout << std::is_same_v<typename Mul<Zero, Zero>::type, Zero> << '\n'; std::cout << std::is_same_v<typename Mul<Zero, Two>::type, Zero> << '\n'; std::cout << std::is_same_v<typename Mul<Two, Two>::type, Four> << '\n'; }
การรวบรวมสิ่งนี้ทำให้เรา
true true true true true true
???
นี่เป็นตอนที่ 1 และหากคุณยังคิดไม่ออก คุณอาจลองแก้ไขส่วนที่เหลือด้วยตัวเอง