C++ Library Extensions 2022.12.09
To help learn modern C++ programming
055-binary_tree07.cpp
Go to the documentation of this file.
1#include <tpf_output.hpp>
2
3/*
4 binary_tree 50 17 72 12 23 54 76 9 14 19 67 > old.txt
5
6 binary_tree 50 17 72 12 23 54 76 9 14 19 67 25 > new.txt
7
8 binary_tree 50 17 72 12 23 54 76 9 14 19 67 25 18> newnew.txt
9
10 https://edotor.net/
11
12 */
13
15
16auto endl = tpf::endl; // single carriage return and flush to console output
17auto endL = tpf::endL; // double carriage returns and flush to console output
18
19auto nl = tpf::nl; // single carriage return without flush
20auto nL = tpf::nL; // double carriage returns without flush
21
22using string_t = std::string;
23
24template<typename ReturnType, typename... Types>
26 std::enable_if_t<tpf::types::is_same_v<tpf::remove_cv_ref_t<Types>...>, ReturnType>;
27
28template<typename ElementType>
29class binary_node
30{
31 public:
32 using node_ptr_t = std::unique_ptr<binary_node>;
33 enum class visit_mode: int{ undefined = 0, pre_order, in_order,
34 ascending_order = in_order, post_order, descending_order };
35
36 enum class find_mode: int { undefined = 0, predecessor = 1, exact_match = 2, successor = 3 };
37
38 enum class child_status: int { left_child = -1, no_child = 0, right_child = 1};
39 private:
40
41 ElementType m_value; // node's value
42
43 mutable int m_height{1}; // the height of a node
44
45 binary_node* m_parent; // pointer to point to the parent node
46
47 node_ptr_t m_left; // left child node
48 node_ptr_t m_right; // right child node
49
50 public:
51
52 int height(bool bRecalculate = false) const
53 {
54 if(bRecalculate) // if bRecalculate is true,
55 { // we will recalculate the m_height member
56 int left_height = 1, right_height = 1;
57
58 if(this->m_left)
59 left_height += this->m_left->height(false); // false means we use cached value of m_height
60
61 if(this->m_right)
62 right_height += this->m_right->height(false); // false means we use cached value of m_height
63
64 // m_height is the greater of the two, left_height and right_height
65 this->m_height = left_height > right_height ?
66 left_height : right_height;
67 }
68
69 return this->m_height;
70 }
71
73 {
74 // the parent ptr of the current node
75 auto parent_ptr = this->m_parent;
76
77 while(parent_ptr)
78 {
79 auto old_height = parent_ptr->m_height;
80
81 if(old_height != parent_ptr->height(true))
82 parent_ptr = parent_ptr->m_parent;
83 else
84 break;
85 }
86 }
87
89 {
90 if(this->m_left && this->m_left.get() == child)
91
93 else if(this->m_right && this->m_right.get() == child)
94
96 else
98 }
99
101 {
102 os << "node_" << this->m_value;
103 return os;
104 }
105
107 {
108 // node_1 [ shape=box, label = " Root "] ;
109 get_node_name(os)
110 << " [ shape=oval, label = \"m_value: "
111 << this->m_value
112 /* << " H:"<< this->m_height */ << " \"] ;";
113
114 return os;
115 }
116
118 {
119 get_node_definition(os) << nl;
120
121 if(this->m_parent)
122 {
123 get_node_name(os) << " -> ";
124 this->m_parent->get_node_name(os);
125
126 auto status = this->m_parent->get_child_status(this);
127
128 if(status == child_status::left_child)
129 os << " [style = dashed, color = red] ; " << nl;
130 else if (status == child_status::right_child)
131 os << " [style = dashed, color = blue] ; " << nl;
132 }
133
134 // root -> left
135 if(this->m_left)
136 {
137 get_node_name(os) << " -> ";
138 this->m_left->get_node_name(os) << " [ color = red ] ;" << nl;
139
140 // recursion for left children
141 this->m_left->print_node(os);
142 }
143
144 // root -> right
145 if(this->m_right)
146 {
147 get_node_name(os) << " -> ";
148 this->m_right->get_node_name(os) << " [color = blue ] ; " << nl;
149
150 // recursion for right children
151 this->m_right->print_node(os);
152 }
153 }
155 {
156 tpf::sstream os;
157
158 os << "digraph G { " << nl;
159
160 print_node(os);
161
162 os <<"}";
163
164 return os.str();
165 }
166
167 const ElementType& get() const { return this->m_value; }
168
169 // if find() fails, returns nullptr,
170 // if successful, it returns the pointer that points
171 // to the node that has the value
172 /* this code is inefficient
173 binary_node* find(ElementType value)
174 {
175 if(value == this->m_value)
176 return this;
177
178 binary_node* ptr = nullptr;
179
180 // recursion
181 if(this->m_left && (ptr = this->m_left->find(value)))
182 return ptr;
183 // recursion
184 if(this->m_right && (ptr = this->m_right->find(value)))
185 return ptr;
186
187 return nullptr;
188 }
189 */
190
191 // if value is not found,
192 // this function returns nullptr
193 binary_node* find(ElementType value)
194 {
195 if(value < this->m_value)
196 {
197 if(this->m_left)
198 return this->m_left->find(value);
199 else
200 return nullptr;
201 }
202 else if (value > this->m_value)
203 {
204 if(this->m_right)
205 return this->m_right->find(value);
206 else
207 return nullptr;
208 }
209 else
210 {
211 return this;
212 }
213 }
214
215 binary_node(ElementType value = ElementType{}, binary_node* parent = nullptr):
216 m_value{value}, m_parent{parent} { }
217
218 // if insertion fails, returns false
219
220 template<typename Type>
221 // we enable this function only when Type and ElementType are same
223 insert(Type&& value)
224 {
225 if(value == this->m_value)
226 return false;
227 else if(value < this->m_value)
228 {
229 if(this->m_left)
230 // this is recursion
231 return this->m_left->insert(std::forward<Type>(value));
232 else
233 {
234 // a new node is inserted and set to this->m_left member
235 // new inserted node is a Leaf node.
236 this->m_left.reset(new binary_node{std::forward<Type>(value), this} );
237
238 // this call updates all m_height members of its parents
239 this->m_left->update_height();
240
241 return true;
242 }
243 }
244 else // value > this->m_value
245 {
246 if(this->m_right)
247 // this is also recursion
248 return this->m_right->insert(std::forward<Type>(value));
249 else
250 {
251 // a new node is inserted and set to this->m_right member
252 // the new inserted node is a LEAF node
253 this->m_right.reset( new binary_node{std::forward<Type>(value), this});
254
255 // update all m_height members of its parents
256 this->m_right->update_height();
257
258 return true;
259 }
260 }
261 }
262
263 // node_ptr_t == std::unique_ptr<binary_node>
264 bool graft(node_ptr_t& node_ptr)
265 {
266 if(node_ptr->m_value < this->m_value)
267 {
268 if(this->m_left)
269 // this is recursion
270 return this->m_left->graft(node_ptr);
271 else
272 {
273 node_ptr->m_parent = this;
274 this->m_left = std::move(node_ptr);
275 this->m_left->update_height();
276
277 return true;
278 }
279 }
280 else // node_ptr->m_value > this->m_value
281 {
282 if(this->m_right)
283 // this is also recursion
284 return this->m_right->graft(node_ptr);
285 else
286 {
287 node_ptr->m_parent = this;
288 this->m_right = std::move(node_ptr);
289 this->m_right->update_height();
290
291 return true;
292 }
293 }
294 }
295
296 // return true only if all parameters are inserted
297 // successfully, otherwise returns false
298 template<typename Type, typename... Types>
299 // we enable this function only when ElementType, Type, and Types... are
300 // of same type
301 enable_if_all_types_are_the_same_t<bool, ElementType, Type, Types...>
302 insert(Type&& arg, Types&&... args)
303 {
304 bool result = this->insert(std::forward<Type>(arg));
305
306 if constexpr(sizeof...(args) != 0)
307 {
308 // recursion
309 return result && this->insert(std::forward<Types>(args)...);
310 }
311 else
312 return result;
313 }
314
315 // for more information about visiting order
316 // Binary Tree (ALL Interview Questions)
317 // https://www.youtube.com/watch?v=VQTF_pRTZek&list=PLeIMaH7i8JDj7DnmO7lll97P1yZjMCpgY&index=2
319 {
320 switch(order)
321 {
323 {
324 os << this->m_value << ", ";
325
326 if(this->m_left)
327 this->m_left->visit_nodes(os, order);
328
329 if(this->m_right)
330 this->m_right->visit_nodes(os, order);
331
332 return;
333 }
335 {
336 if(this->m_left)
337 this->m_left->visit_nodes(os, order);
338
339 if(this->m_right)
340 this->m_right->visit_nodes(os, order);
341
342 os << this->m_value << ", ";
343
344 return;
345 }
346
348 {
349 if(this->m_right)
350 this->m_right->visit_nodes(os, order);
351
352 os << this->m_value << ", ";
353
354 if(this->m_left)
355 this->m_left->visit_nodes(os, order);
356
357 return;
358 }
359
361 default: // in_order
362 {
363 if(this->m_left)
364 this->m_left->visit_nodes(os, order);
365
366 os << this->m_value << ", ";
367
368 if(this->m_right)
369 this->m_right->visit_nodes(os, order);
370
371 return;
372 }
373 }
374 }
375
376 // if fails, return nullptr
377 binary_node* find(ElementType value, find_mode fmode,
379 {
380 binary_node* ptr = nullptr;
381
382 if(((vmode==visit_mode::ascending_order) && (fmode==find_mode::successor))||
384 {
385 if(this->m_left && (ptr = this->m_left->find(value, fmode, vmode)))
386 return ptr;
387
388 if( value < this->m_value)
389 return this;
390
391 if(this->m_right && (ptr = this->m_right->find(value, fmode, vmode)))
392 return ptr;
393
394 // we failed
395 return ptr;
396 }
397 else if(( (vmode == visit_mode::ascending_order)&&(fmode==find_mode::predecessor)) ||
399 {
400 if(this->m_right && (ptr = this->m_right->find(value, fmode, vmode)))
401 return ptr;
402
403 if(value > this->m_value)
404 return this;
405
406 if(this->m_left && (ptr = this->m_left->find(value, fmode, vmode)))
407 return ptr;
408
409 return ptr;
410 }
411 else if(fmode == find_mode::exact_match)
412 return this->find(value);
413 else
414 return nullptr;
415 }
416
418 {
419 // both m_left and m_right are nullptr
420 return (!this->m_left && !this->m_right);
421 }
422
424 {
425 if(this->m_left && (ptr == this->m_left.get()))
426 return std::move(this->m_left);
427 else if(this->m_right && (ptr == this->m_right.get()))
428 return std::move(this->m_right);
429 else
430 return nullptr;
431 }
432
433 static bool remove_node(node_ptr_t& root_ptr, ElementType value)
434 {
435 binary_node* ptr = root_ptr->find(value);
436
437 // node with "value" does not exist
438 if(!ptr) return false;
439
440 // if parent is nullptr, then it is root node
441 if(!ptr->m_parent)
442 {
443 // ptr is a root node of type binary_node*
444 // root_ptr is a root node of type
445 // std::unique_ptr<binary_node>;
446
447 // we move left child to left_child
448 // after move, root_ptr->m_left is invalid
449 node_ptr_t left_child
450 = std::move(root_ptr->m_left);
451
452 // after move, root_ptr->m_right is invalid
453 node_ptr_t right_child
454 = std::move(root_ptr->m_right);
455
456 if(right_child) // new root
457 {
458 // because root node does not have its parent.
459 right_child->m_parent = nullptr;
460
461 // existing root_ptr is destroyed
462 // and right_child is moved to root_ptr
463 // at this point, existing old root is
464 // properly released.
465 root_ptr = std::move(right_child);
466
467 // do not forget this part
468 if(left_child)
469 root_ptr->graft(left_child);
470
471 return true;
472 }
473 else if(left_child) // right_child is nullptr
474 // left_child is a new root.
475 {
476 // because root node does not have its parent.
477 left_child->m_parent = nullptr;
478
479 // after move, the older root is released
480 // and left_child is moved to root_ptr
481 root_ptr = std::move(left_child);
482
483 return true;
484 }
485 else
486 {
487 // since we destroyed the root
488 // we should not access the root after this statment
489 root_ptr.reset();
490
491 return true;
492 }
493 }
494 else // non-root node
495 {
496 // ptr is the node to remove
497 // is not a root node.
498
499 if(ptr->is_leaf_node())
500 {
501 // this code cuts off the relation
502 // between the child node and its parent.
503 node_ptr_t orphan =
504 ptr->m_parent->release_child(ptr);
505
506 // when the orphan goes off this block,
507 // the allocated memory for orphan is
508 // cleaned up. and orphan.get() == ptr
509
510 if(ptr->m_parent->is_leaf_node())
511 {
512 ptr->m_parent->m_height = 1;
513 ptr->m_parent->update_height();
514 }
515
516 return true;
517 }
518 else // not a leaf node,
519 {
520 // cut off the relationship between ptr
521 // and its parent.
522 node_ptr_t orphan =
523 ptr->m_parent->release_child(ptr);
524
525 // this is for update the height of the parents
526 // nodes
527 ptr->m_parent->height(true);
528 ptr->m_parent->update_height();
529
530 node_ptr_t left_child =
531 std::move(orphan->m_left);
532
533 node_ptr_t right_child =
534 std::move(orphan->m_right);
535
536 if(right_child)
537 root_ptr->graft(right_child);
538
539 if(left_child)
540 root_ptr->graft(left_child);
541
542 return true;
543 }
544 }
545 }
546}; // end of binary_node class
547
548// std::unique_ptr<binary_node<ElementType>>
549template<typename ElementType>
551
552template<typename ElementType>
553void test_remove_node(node_ptr_t<ElementType>& node_ptr, ElementType value)
554{
555 using binary_node_t = binary_node<ElementType>;
556 using node_ptr_t = std::unique_ptr<binary_node_t>;
557
558 binary_node_t::remove_node(node_ptr, value);
559
560 if(node_ptr)
561 {
562 stream << node_ptr->build_digraph() << endl;
563 }
564
565}
566
568{
569 // In courtesy of Vivekanand Khyade - Algorithm Every Day
570 // Introduction to AVL tree (Why AVL tree is needed?)
571 // https://youtu.be/5Q-__zhQ2Gs?t=10
572
573 binary_node<int> root{10};
574
576
577 root.insert(8);
578 root.insert(15);
579
580 root.insert(7);
581 root.insert(9);
582 root.insert(12);
583 root.insert(17);
584
585 root.insert(6);
586 root.insert(16);
587 root.insert(18);
588
589 tpf::sstream os;
590
591 root.visit_nodes(os, visit_mode::ascending_order);
592 stream << "Ascending-order: " << endL;
593 stream << os.str() << endL;
594
595 os.clear();
596
597 root.visit_nodes(os, visit_mode::descending_order);
598 stream << "Descending-order: " << endL;
599 stream << os.str() << endL;
600}
601
603{
604 // In courtesy of Vivekanand Khyade - Algorithm Every Day
605 // Introduction to AVL tree (Why AVL tree is needed?)
606 // https://youtu.be/5Q-__zhQ2Gs?t=10
607
608 binary_node<int> root{10};
609
611
612 root.insert(8, 15, 7, 9, 12, 17, 6, 16, 18, 25);
613
614 if(auto ptr = root.find(6))
615 {
616 stream << "Value found: " << ptr->get() << endl;
617 }
618 else
619 {
620 stream <<"Value not found " << endl;
621 }
622
623 if(auto ptr = root.find(20) )
624 {
625 stream << "Value found: " << ptr->get() << endl;
626 }
627 else
628 {
629 stream << "Value not found " << endl;
630 }
631}
632
633
635{
636 // In courtesy of Vivekanand Khyade - Algorithm Every Day
637 // Introduction to AVL tree (Why AVL tree is needed?)
638 // https://youtu.be/5Q-__zhQ2Gs?t=10
639
640 binary_node<int> root{10};
641
643
644 root.insert(8, 15, 7, 9, 12, 17, 6, 16, 18);
645
646 stream << root.build_digraph() << endl;
647
648}
649
650void print_usage(string_t appname, const char* msg)
651{
652 stream << "Message: " << msg << endL;
653
654 stream << "Usage 1: " << appname << " graph node_list " << endL;
655 stream << "\tExample: " << appname <<" graph 10 8 15 7 9 12 17 6 16 18 > binary_tree.gv" << endL;
656 stream << "\tdot -Tpng binary_tree.gv -o binary_tree.png" << endL;
657
658 stream << "Usage 2: " << appname << " list node_list " << endL;
659 stream << "\tExample: " << appname <<" list 10 8 15 7 9 12 17 6 16 18" << endL;
660
661 stream << "Usage 3: " << appname << " {ascending|desecnding} {successor_of|predecessor_of|exact_match_of} value node_list " << endL;
662 stream << "\tExample: " << appname <<" ascending successor_of 19 10 8 15 7 9 12 17 6 16 18" << endL;
663 stream << "\t\t" <<" Finds the successor of 19 in ascending order from the list 10 8 15 7 9 12 17 6 16 18" << endL;
664
665 stream << "Usage 4: " << appname << " remove value node_list " << endL;
666 stream << "\tExample: " << appname <<" remove 19 10 8 15 7 9 12 17 6 16 18" << endL;
667 stream << "\t\t" <<" Find and remove node with value 19 from the list 10 8 15 7 9 12 17 6 16 18" << endL;
668
669 stream << "Usage 5: " << appname << " find value node_list " << endL;
670 stream << "\tExample: " << appname <<" find 19 10 8 15 7 9 12 17 6 16 18" << endL;
671 stream << "\t\t" <<" Find 19 from the list 19 10 8 15 7 9 12 17 6 16 18" << endL;
672}
673
674int main(int argc, const char* argv[])
675{
676 // test_ascending_descending_order();
677 // test_find_binary_tree();
678 // test_build_digraph();
679
680 string_t appname{argv[0]};
681
682 if(argc < 2)
683 {
684 print_usage(appname, "Program commandline syntax");
685
686 return 0;
687 }
688
689 string_t command{argv[1]};
690
691 if(command == "graph")
692 {
693 binary_node<int> root { std::atoi(argv[2])};
694
695 for(int i = 3; i < argc; ++i)
696 {
697 root.insert( std::atoi(argv[i]) );
698 }
699
700 stream << root.build_digraph() << endl;
701 }
702 else if(command == "list")
703 {
704 binary_node<int> root { std::atoi(argv[2])};
706
707 for(int i = 3; i < argc; ++i)
708 {
709 root.insert( std::atoi(argv[i]) );
710 }
711
712 stream <<"Ascending order: ";
713 root.visit_nodes(stream, visit_mode::ascending_order);
714 stream << endl;
715
716 stream <<"Descending order: ";
717 root.visit_nodes(stream, visit_mode::descending_order);
718 stream << endl;
719 }
720 else if(command == "ascending"
721 || command == "descending")
722 {
724 using find_mode = binary_node<int>::find_mode;
725
726 visit_mode vmode {visit_mode::undefined};
727 find_mode fmode {find_mode::undefined};
728
729 if(command == "ascending")
730 vmode = visit_mode::ascending_order;
731
732 if(command == "descending")
733 vmode = visit_mode::descending_order;
734
735 if(vmode==visit_mode::undefined)
736 {
737 print_usage(appname, "Undefined visit mode");
738 return 0;
739 }
740
741 if(argc < 5)
742 {
743 print_usage(appname, "Syntax Error");
744 return 0;
745 }
746
747 string_t operation{argv[2]};
748
749 if(operation == "successor_of")
750 fmode = find_mode::successor;
751
752 if(operation == "predecessor_of")
753 fmode = find_mode::predecessor;
754
755 if(operation == "exact_match_of")
756 fmode = find_mode::exact_match;
757
758 if(fmode == find_mode::undefined)
759 {
760 print_usage(appname, "Undefined find mode");
761 return 0;
762 }
763
764 int value = std::atoi(argv[3]);
765
766 binary_node<int> root { std::atoi(argv[4])};
768
769 for(int i = 5; i < argc; ++i)
770 {
771 root.insert( std::atoi(argv[i]) );
772 }
773
774 stream <<"Ascending order: ";
775 root.visit_nodes(stream, visit_mode::ascending_order);
776 stream << endl;
777
778 stream <<"Descending order: ";
779 root.visit_nodes(stream, visit_mode::descending_order);
780 stream << endl;
781
782 auto ptr = root.find(value, fmode, vmode);
783
784 if(ptr)
785 {
786 if(fmode == find_mode::successor)
787 stream << "The successor of " << value;
788 else if (fmode == find_mode::predecessor)
789 stream << "The predecessor of " << value;
790 else if (fmode == find_mode::exact_match)
791 stream << "The exact match of " << value;
792
793 if(vmode == visit_mode::ascending_order)
794 stream << " in ascending order is ";
795
796 if(vmode == visit_mode::descending_order)
797 stream << " in descending order is ";
798
799 stream << ptr->get() << endL;
800 }
801 else
802 {
803 if(fmode == find_mode::successor)
804 stream << "The successor of " << value;
805 else if (fmode == find_mode::predecessor)
806 stream << "The predecessor of " << value;
807 else if (fmode == find_mode::exact_match)
808 stream << "The exact match of " << value;
809
810 if(vmode == visit_mode::ascending_order)
811 stream << " in ascending order is NOT found";
812
813 if(vmode == visit_mode::descending_order)
814 stream << " in descending order NOT found ";
815
816 stream << endL;
817 }
818 }
819 else if(command == "remove")
820 {
821 using binary_node_t = binary_node<int>;
822 using node_ptr_t = std::unique_ptr<binary_node_t>;
823
824 // binary_tree remove 10 10
825 if(argc < 4)
826 {
827 print_usage(appname, "Syntax Error");
828 return 0;
829 }
830
831 int value = std::atoi(argv[2]);
832
833 node_ptr_t root = std::make_unique<binary_node_t>(std::atoi(argv[3]));
834
835 for(int i = 4; i < argc; ++i)
836 {
837 root->insert( std::atoi(argv[i]) );
838 }
839
840 test_remove_node(root, value);
841 }
842 else if(command == "find")
843 {
844 using binary_node_t = binary_node<int>;
845 using node_ptr_t = std::unique_ptr<binary_node_t>;
846
847 // binary_tree remove 10 10
848 if(argc < 4)
849 {
850 print_usage(appname, "Syntax Error");
851 return 0;
852 }
853
854 int value = std::atoi(argv[2]);
855
856 node_ptr_t root = std::make_unique<binary_node_t>(std::atoi(argv[3]));
857
858 for(int i = 4; i < argc; ++i)
859 {
860 root->insert( std::atoi(argv[i]) );
861 }
862
863 binary_node_t* ptr = root->find(value);
864
865 if(ptr)
866 {
867 stream << "We found value: " << value << endl;
868 }
869 else
870 {
871 stream << "We failed to find the value: " << value << endl;
872 }
873 }
874}
std::string string_t
std::enable_if_t< tpf::types::is_same_v< tpf::remove_cv_ref_t< Types >... >, ReturnType > enable_if_all_types_are_the_same_t
typename binary_node< ElementType >::node_ptr_t node_ptr_t
auto endL
tpf::sstream stream
auto nL
void test_remove_node(node_ptr_t< ElementType > &node_ptr, ElementType value)
void print_usage(string_t appname, const char *msg)
auto endl
void test_ascending_descending_order()
void test_find_binary_tree()
int main(int argc, const char *argv[])
auto nl
void test_build_digraph()
typename graph_t::visit_mode visit_mode
Definition: 060-graph02.cpp:8
static bool remove_node(node_ptr_t &root_ptr, ElementType value)
binary_node * find(ElementType value)
enable_if_all_types_are_the_same_t< bool, ElementType, Type, Types... > insert(Type &&arg, Types &&... args)
child_status get_child_status(binary_node *child)
tpf::sstream & get_node_name(tpf::sstream &os)
bool graft(node_ptr_t &node_ptr)
ElementType get() const
string_t build_digraph()
const ElementType & get() const
node_ptr_t release_child(binary_node *ptr)
void print_node(tpf::sstream &os)
std::unique_ptr< binary_node > node_ptr_t
tpf::sstream & get_node_definition(tpf::sstream &os)
bool insert(ElementType value)
enable_if_all_types_are_the_same_t< bool, ElementType, Type > insert(Type &&value)
binary_node * find(ElementType value, find_mode fmode, visit_mode vmode=visit_mode::ascending_order)
binary_node(ElementType value=ElementType{}, binary_node *parent=nullptr)
int height(bool bRecalculate=false) const
void visit_nodes(tpf::sstream &os, visit_mode order=visit_mode::in_order)
std::string str() const
Definition: tpf_output.hpp:951
string_stream & clear()
Definition: tpf_output.hpp:916
constexpr auto endL
Definition: tpf_output.hpp:974
constexpr auto nL
Definition: tpf_output.hpp:972
constexpr auto endl
Definition: tpf_output.hpp:973
constexpr auto nl
Definition: tpf_output.hpp:971
Stream output operators << are implemented.