LibSWOC++ 1.5.14
Solid Wall of C++
Loading...
Searching...
No Matches
IntrusiveDList.h
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright Apach Software Foundation 2019
12
13#pragma once
14
16#include <iterator>
17#include <type_traits>
18
19#include "swoc/swoc_version.h"
20
21namespace swoc { inline namespace SWOC_VERSION_NS {
71template <typename L> class IntrusiveDList {
72 friend class iterator;
73
74public:
75 using self_type = IntrusiveDList;
77 // Get the result of calling the pointer access function, then strip off reference and pointer
78 // qualifiers. @c nullptr is used instead of the pointer type because this is done precisely
79 // to find that type.
80 using value_type = typename std::remove_pointer<
81 typename std::remove_reference<typename std::invoke_result<decltype(L::next_ptr), std::nullptr_t>::type>::type>::type;
82
84 class const_iterator {
85 using self_type = const_iterator;
86 friend class IntrusiveDList;
87
88 public:
89 using list_type = IntrusiveDList;
90 using value_type = const typename list_type::value_type;
91 // STL algorithm compliance.
92 using iterator_category = std::bidirectional_iterator_tag;
93 using pointer = value_type *;
94 using reference = value_type &;
95 using difference_type = int;
96
98 const_iterator();
99
103 self_type &operator++();
104
108 self_type &operator--();
109
113 self_type operator++(int);
114
118 self_type operator--(int);
119
122 value_type &operator*() const;
123
126 value_type *operator->() const;
127
131 operator value_type *() const;
132
134 bool operator==(self_type const &that) const;
135
137 bool operator!=(self_type const &that) const;
138
142 bool has_prev() const;
143
146 bool has_next() const;
147
151 bool has_predecessor() const;
152
158 bool has_successor() const;
159
160 protected:
161 // These are stored non-const to make implementing @c iterator easier. This class provides the required @c const
162 // protection.
163 list_type *_list{nullptr};
164 typename list_type::value_type *_v{nullptr};
165
167 const_iterator(const list_type *list, value_type *v);
168 };
169
171 class iterator : public const_iterator {
172 using self_type = iterator;
173 using super_type = const_iterator;
174
175 friend class IntrusiveDList;
176
177 public:
178 using list_type = IntrusiveDList;
179 using value_type = typename list_type::value_type;
180 // STL algorithm compliance.
181 using iterator_category = std::bidirectional_iterator_tag;
182 using pointer = value_type *;
183 using reference = value_type &;
184
186 iterator();
187
191 self_type &operator++();
192
196 self_type &operator--();
197
201 self_type operator++(int);
202
206 self_type operator--(int);
207
210 value_type &operator*() const;
211
214 value_type *operator->() const;
215
219 operator value_type *() const;
220
221 protected:
223 iterator(list_type *list, value_type *v);
224 };
225
227 IntrusiveDList() = default;
228
229 IntrusiveDList(self_type const &that) = delete;
230
232 IntrusiveDList(self_type &&that);
233
235 self_type &operator=(const self_type &that) = delete;
236
238 self_type &operator=(self_type &&that);
239
242 bool empty() const;
243
246 bool contains(const value_type *v) const;
247
250 self_type &prepend(value_type *v);
251
254 self_type &append(value_type *v);
255
258 value_type *take_head();
259
262 value_type *take_tail();
263
267 self_type &insert_after(value_type *target, value_type *v);
268
272 self_type &insert_before(value_type *target, value_type *v);
273
277 self_type &insert_after(iterator const &target, value_type *v);
278
282 self_type &insert_before(iterator const &target, value_type *v);
283
286 value_type *erase(value_type *v);
287
290 iterator erase(const iterator &loc);
291
295 iterator erase(const iterator &start, const iterator &limit);
296
304 iterator nth(unsigned n);
305
313 self_type take_prefix(unsigned n);
314
322 self_type split_prefix(unsigned n);
323
331 self_type take_suffix(unsigned n);
332
340 self_type split_suffix(unsigned n);
341
349 self_type &append(self_type &src);
350
358 self_type &prepend(self_type &src);
359
366 self_type &insert_after(value_type *target, self_type &src);
367
374 self_type &insert_before(value_type *target, self_type &src);
375
382 self_type &insert_after(iterator const &target, self_type &src);
383
390 self_type &insert_before(iterator const &target, self_type &src);
391
395 self_type &clear();
396
398 size_t count() const;
399
401 iterator begin();
402
404 const_iterator begin() const;
405
407 iterator end();
408
410 const_iterator end() const;
411
420 iterator iterator_for(value_type *v);
421
422 const_iterator iterator_for(const value_type *v) const;
423
425 value_type *head();
426
427 const value_type *head() const;
428
430 value_type *tail();
431
432 const value_type *tail() const;
433
437 template <typename F> self_type &apply(F &&f);
438
439protected:
440 value_type *_head{nullptr};
441 value_type *_tail{nullptr};
442 size_t _count{0};
443};
444
465template <typename T, T *(T::*NEXT) = &T::_next, T *(T::*PREV) = &T::_prev> struct IntrusiveLinkage {
466 static T *&next_ptr(T *thing);
467 static T *&prev_ptr(T *thing);
468};
469
470template <typename T, T *(T::*NEXT), T *(T::*PREV)>
471T *&
472IntrusiveLinkage<T, NEXT, PREV>::next_ptr(T *thing) {
473 return thing->*NEXT;
474}
475
476template <typename T, T *(T::*NEXT), T *(T::*PREV)>
477T *&
478IntrusiveLinkage<T, NEXT, PREV>::prev_ptr(T *thing) {
479 return thing->*PREV;
480}
481
508template <typename T, typename P>
509T *&
510ptr_ref_cast(P *&p) {
511 union {
512 P **_p;
513 T **_t;
514 } u{&p};
515 return *(u._t);
516}
517
538template <typename T, typename L> struct IntrusiveLinkageRebind {
539 static T *&next_ptr(T *thing);
540 static T *&prev_ptr(T *thing);
541};
542
543template <typename T, typename L>
544T *&
545IntrusiveLinkageRebind<T, L>::next_ptr(T *thing) {
546 return ptr_ref_cast<T>(L::next_ptr(thing));
547}
548
549template <typename T, typename L>
550T *&
551IntrusiveLinkageRebind<T, L>::prev_ptr(T *thing) {
552 return ptr_ref_cast<T>(L::prev_ptr(thing));
553}
554
555template <typename T> struct IntrusiveLinks {
556 T *_next = nullptr;
557 T *_prev = nullptr;
558};
559
560template <typename T, IntrusiveLinks<T>(T::*links)> struct IntrusiveLinkDescriptor {
561 static T *&next_ptr(T *thing);
562 static T *&prev_ptr(T *thing);
563};
564
565// --- Implementation ---
566
567template <typename L> IntrusiveDList<L>::const_iterator::const_iterator() {}
568
569template <typename L>
570IntrusiveDList<L>::const_iterator::const_iterator(const list_type *list, value_type *v)
571 : _list(const_cast<list_type *>(list)), _v(const_cast<typename list_type::value_type *>(v)) {}
572
573template <typename L> IntrusiveDList<L>::iterator::iterator() {}
574
575template <typename L> IntrusiveDList<L>::iterator::iterator(list_type *list, value_type *v) : super_type(list, v) {}
576
577template <typename L>
578auto
579IntrusiveDList<L>::const_iterator::operator++() -> self_type & {
580 _v = L::next_ptr(_v);
581 return *this;
582}
583
584template <typename L>
585auto
586IntrusiveDList<L>::iterator::operator++() -> self_type & {
587 this->super_type::operator++();
588 return *this;
589}
590
591template <typename L>
592auto
593IntrusiveDList<L>::const_iterator::operator++(int) -> self_type {
594 self_type tmp(*this);
595 ++*this;
596 return tmp;
597}
598
599template <typename L>
600auto
601IntrusiveDList<L>::iterator::operator++(int) -> self_type {
602 self_type tmp(*this);
603 ++*this;
604 return tmp;
605}
606
607template <typename L>
608auto
609IntrusiveDList<L>::const_iterator::operator--() -> self_type & {
610 if (_v) {
611 _v = L::prev_ptr(_v);
612 } else if (_list) {
613 _v = _list->_tail;
614 }
615 return *this;
616}
617
618template <typename L>
619auto
620IntrusiveDList<L>::iterator::operator--() -> self_type & {
621 this->super_type::operator--();
622 return *this;
623}
624
625template <typename L>
626auto
627IntrusiveDList<L>::const_iterator::operator--(int) -> self_type {
628 self_type tmp(*this);
629 --*this;
630 return tmp;
631}
632
633template <typename L>
634auto
635IntrusiveDList<L>::iterator::operator--(int) -> self_type {
636 self_type tmp(*this);
637 --*this;
638 return tmp;
639}
640
641template <typename L>
642auto
643IntrusiveDList<L>::const_iterator::operator->() const -> value_type * {
644 return _v;
645}
646
647template <typename L>
648auto
649IntrusiveDList<L>::iterator::operator->() const -> value_type * {
650 return super_type::_v;
651}
652
653template <typename L> IntrusiveDList<L>::const_iterator::operator value_type *() const {
654 return _v;
655}
656
657template <typename L>
658auto
659IntrusiveDList<L>::const_iterator::operator*() const -> value_type & {
660 return *_v;
661}
662
663template <typename L>
664auto
665IntrusiveDList<L>::iterator::operator*() const -> value_type & {
666 return *super_type::_v;
667}
668
669template <typename L> IntrusiveDList<L>::iterator::operator value_type *() const {
670 return super_type::_v;
671}
672
674
675template <typename L>
676IntrusiveDList<L>::IntrusiveDList(self_type &&that) : _head(that._head), _tail(that._tail), _count(that._count) {
677 that.clear();
678}
679
680template <typename L>
681bool
682IntrusiveDList<L>::empty() const {
683 return _head == nullptr;
684}
685
686template <typename L>
687bool
688IntrusiveDList<L>::contains(const value_type *v) const {
689 for (auto thing = _head; thing; thing = L::next_ptr(thing)) {
690 if (thing == v)
691 return true;
692 }
693 return false;
694}
695
696template <typename L>
697bool
698IntrusiveDList<L>::const_iterator::operator==(self_type const &that) const {
699 return this->_v == that._v;
700}
701
702template <typename L>
703bool
704IntrusiveDList<L>::const_iterator::operator!=(self_type const &that) const {
705 return this->_v != that._v;
706}
707
708template <typename L>
709bool
710IntrusiveDList<L>::const_iterator::has_predecessor() const {
711 return _v ? nullptr != L::prev_ptr(_v) : !_list->empty();
712}
713
714template <typename L>
715bool
716IntrusiveDList<L>::const_iterator::has_prev() const {
717 return _v ? nullptr != L::prev_ptr(_v) : !_list->empty();
718}
719
720template <typename L>
721bool
722IntrusiveDList<L>::const_iterator::has_successor() const {
723 return _v && nullptr != L::next_ptr(_v);
724}
725
726template <typename L>
727bool
728IntrusiveDList<L>::const_iterator::has_next() const {
729 return nullptr != _v;
730}
731
732template <typename L>
733auto
734IntrusiveDList<L>::prepend(value_type *v) -> self_type & {
735 L::prev_ptr(v) = nullptr;
736 if (nullptr != (L::next_ptr(v) = _head)) {
737 L::prev_ptr(_head) = v;
738 } else {
739 _tail = v; // transition empty -> non-empty
740 }
741 _head = v;
742 ++_count;
743 return *this;
744}
745
746template <typename L>
747auto
748IntrusiveDList<L>::append(value_type *v) -> self_type & {
749 L::next_ptr(v) = nullptr;
750 if (nullptr != (L::prev_ptr(v) = _tail)) {
751 L::next_ptr(_tail) = v;
752 } else {
753 _head = v; // transition empty -> non-empty
754 }
755 _tail = v;
756 ++_count;
757 return *this;
758}
759
760template <typename L>
761auto
762IntrusiveDList<L>::take_head() -> value_type * {
763 value_type *zret = _head;
764 if (_head) {
765 if (nullptr == (_head = L::next_ptr(_head))) {
766 _tail = nullptr; // transition non-empty -> empty
767 } else {
768 L::prev_ptr(_head) = nullptr;
769 }
770 L::next_ptr(zret) = L::prev_ptr(zret) = nullptr;
771 --_count;
772 }
773 return zret;
774}
775
776template <typename L>
777auto
778IntrusiveDList<L>::take_tail() -> value_type * {
779 value_type *zret = _tail;
780 if (_tail) {
781 if (nullptr == (_tail = L::prev_ptr(_tail))) {
782 _head = nullptr; // transition non-empty -> empty
783 } else {
784 L::next_ptr(_tail) = nullptr;
785 }
786 L::next_ptr(zret) = L::prev_ptr(zret) = nullptr;
787 --_count;
788 }
789 return zret;
790}
791
792template <typename L>
793auto
794IntrusiveDList<L>::insert_after(value_type *target, value_type *v) -> self_type & {
795 if (target) {
796 if (nullptr != (L::next_ptr(v) = L::next_ptr(target))) {
797 L::prev_ptr(L::next_ptr(v)) = v;
798 } else if (_tail == target) {
799 _tail = v;
800 }
801 L::prev_ptr(v) = target;
802 L::next_ptr(target) = v;
803
804 ++_count;
805 } else {
806 this->append(v);
807 }
808 return *this;
809}
810
811template <typename L>
812auto
813IntrusiveDList<L>::insert_after(iterator const &target, value_type *v) -> self_type & {
814 return this->insert_after(target._v, v);
815}
816
817template <typename L>
818auto
819IntrusiveDList<L>::insert_before(value_type *target, value_type *v) -> self_type & {
820 if (target) {
821 if (nullptr != (L::prev_ptr(v) = L::prev_ptr(target))) {
822 L::next_ptr(L::prev_ptr(v)) = v;
823 } else if (target == _head) {
824 _head = v;
825 }
826 L::next_ptr(v) = target;
827 L::prev_ptr(target) = v;
828
829 ++_count;
830 } else {
831 this->append(v);
832 }
833 return *this;
834}
835
836template <typename L>
837auto
838IntrusiveDList<L>::insert_before(iterator const &target, value_type *v) -> self_type & {
839 return this->insert_before(target._v, v);
840}
841
842template <typename L>
843auto
844IntrusiveDList<L>::insert_after(value_type *target, self_type &src) -> self_type & {
845 if (target && src._count > 0) {
846 if (_tail == target) {
847 this->append(src);
848 } else { // invariant - @a target is not tail therefore has a successor.
849 L::next_ptr(src._tail) = L::next_ptr(target); // link @a src tail to @a target successor
850 L::prev_ptr(L::next_ptr(src._tail)) = src._tail;
851
852 L::prev_ptr(src._head) = target; // link @a src head to @target
853 L::next_ptr(target) = src._head;
854
855 _count += src._count;
856 src.clear();
857 }
858 }
859 return *this;
860}
861
862template <typename L>
863auto
864IntrusiveDList<L>::insert_after(iterator const &target, self_type &src) -> self_type & {
865 return this->insert_after(target._v, src);
866}
867
868template <typename L>
869auto
870IntrusiveDList<L>::insert_before(value_type *target, self_type &src) -> self_type & {
871 if (target && src._count > 0) {
872 if (_head == target) {
873 this->prepend(src);
874 } else { // invariant - @a target is not head and therefore has a predecessor.
875 L::prev_ptr(src._head) = L::prev_ptr(target); // link @a src head to @a target predecessor
876 L::next_ptr(L::prev_ptr(target)) = src._head;
877
878 L::next_ptr(src._tail) = target; // link @a src tail to @a target
879 L::prev_ptr(target) = src._tail;
880
881 _count += src._count;
882 src.clear();
883 }
884 }
885 return *this;
886}
887
888template <typename L>
889auto
890IntrusiveDList<L>::insert_before(iterator const &target, self_type &src) -> self_type & {
891 return this->insert_before(target._v, src);
892}
893
894template <typename L>
895auto
896IntrusiveDList<L>::erase(value_type *v) -> value_type * {
897 value_type *zret{nullptr};
898
899 if (L::prev_ptr(v)) {
900 L::next_ptr(L::prev_ptr(v)) = L::next_ptr(v);
901 }
902 if (L::next_ptr(v)) {
903 zret = L::next_ptr(v);
904 L::prev_ptr(L::next_ptr(v)) = L::prev_ptr(v);
905 }
906 if (v == _head) {
907 _head = L::next_ptr(v);
908 }
909 if (v == _tail) {
910 _tail = L::prev_ptr(v);
911 }
912 L::prev_ptr(v) = L::next_ptr(v) = nullptr;
913 --_count;
914
915 return zret;
916}
917
918template <typename L>
919auto
920IntrusiveDList<L>::erase(const iterator &loc) -> iterator {
921 return this->iterator_for(this->erase(loc._v));
922}
923
924template <typename L>
925auto
926IntrusiveDList<L>::erase(const iterator &first, const iterator &limit) -> iterator {
927 value_type *spot = first;
928 value_type *prev{L::prev_ptr(spot)};
929 if (prev) {
930 L::next_ptr(prev) = limit;
931 }
932 if (spot == _head) {
933 _head = limit;
934 }
935 // tail is only updated if @a limit is @a end (e.g., @c nullptr).
936 if (nullptr == limit) {
937 _tail = prev;
938 } else {
939 L::prev_ptr(limit) = prev;
940 }
941 // Clear links in removed elements.
942 while (spot != limit) {
943 value_type *target{spot};
944 spot = L::next_ptr(spot);
945 L::prev_ptr(target) = L::next_ptr(target) = nullptr;
946 };
947
948 return {limit._v, this};
949}
950
951template <typename L>
952auto
953IntrusiveDList<L>::operator=(self_type &&that) -> self_type & {
954 if (this != &that) {
955 this->_head = that._head;
956 this->_tail = that._tail;
957 this->_count = that._count;
958 that.clear();
959 }
960 return *this;
961}
962
963template <typename L>
964size_t
965IntrusiveDList<L>::count() const {
966 return _count;
967}
968
969template <typename L>
970auto
971IntrusiveDList<L>::begin() const -> const_iterator {
972 return const_iterator{this, _head};
973}
974
975template <typename L>
976auto
977IntrusiveDList<L>::begin() -> iterator {
978 return iterator{this, _head};
979}
980
981template <typename L>
982auto
983IntrusiveDList<L>::end() const -> const_iterator {
984 return const_iterator{this, nullptr};
985}
986
987template <typename L>
988auto
989IntrusiveDList<L>::end() -> iterator {
990 return iterator{this, nullptr};
991}
992
993template <typename L>
994auto
995IntrusiveDList<L>::iterator_for(value_type *v) -> iterator {
996 return iterator{this, v};
997}
998
999template <typename L>
1000auto
1001IntrusiveDList<L>::iterator_for(const value_type *v) const -> const_iterator {
1002 return const_iterator{this, v};
1003}
1004
1005template <typename L>
1006auto
1007IntrusiveDList<L>::tail() -> value_type * {
1008 return _tail;
1009}
1010
1011template <typename L>
1012auto
1013IntrusiveDList<L>::tail() const -> const value_type * {
1014 return _tail;
1015}
1016
1017template <typename L>
1018auto
1019IntrusiveDList<L>::head() -> value_type * {
1020 return _head;
1021}
1022
1023template <typename L>
1024auto
1025IntrusiveDList<L>::head() const -> const value_type * {
1026 return _head;
1027}
1028
1029template <typename L>
1030auto
1031IntrusiveDList<L>::clear() -> self_type & {
1032 _head = _tail = nullptr;
1033 _count = 0;
1034 return *this;
1035}
1036
1037template <typename L>
1038auto
1039IntrusiveDList<L>::nth(unsigned int n) -> iterator {
1040 if (n >= _count) {
1041 return {};
1042 }
1043
1044 value_type *spot;
1045 if (n < _count / 2) { // closer to head, count from there..
1046 spot = _head;
1047 while (n-- > 0) {
1048 spot = L::next_ptr(spot);
1049 }
1050 } else { // count from tail
1051 spot = _tail;
1052 unsigned idx = _count - 1;
1053 while (idx-- > n) {
1054 spot = L::prev_ptr(spot);
1055 }
1056 }
1057 return iterator_for(spot);
1058}
1059
1060template <typename L>
1061auto
1062IntrusiveDList<L>::take_prefix(unsigned int n) -> self_type {
1063 if (n == 0) {
1064 return {};
1065 }
1066
1067 if (_count <= n) {
1068 return std::move(*this);
1069 }
1070
1071 // Invariant - there is at least one element that will not be taken.
1072
1073 self_type zret;
1074 value_type *spot = this->nth(n); // new @a head for @a this
1075
1076 zret._count = n;
1077 zret._head = _head;
1078 zret._tail = L::prev_ptr(spot);
1079 L::next_ptr(zret._tail) = nullptr;
1080
1081 _count -= n;
1082 _head = spot;
1083 L::prev_ptr(_head) = nullptr;
1084
1085 return zret;
1086}
1087
1088template <typename L>
1089auto
1090IntrusiveDList<L>::split_prefix(unsigned int n) -> self_type {
1091 if (n <= _count) {
1092 return this->take_prefix(n);
1093 }
1094 return {};
1095}
1096
1097template <typename L>
1098auto
1099IntrusiveDList<L>::take_suffix(unsigned int n) -> self_type {
1100 if (n == 0) {
1101 return {};
1102 }
1103
1104 if (_count <= n) {
1105 return std::move(*this);
1106 }
1107
1108 // Invariant - there is at least one element that will not be taken.
1109
1110 self_type zret;
1111 value_type *spot = this->nth(_count - n - 1); // new @a tail for @a this
1112
1113 zret._count = n;
1114 zret._head = L::next_ptr(spot);
1115 L::prev_ptr(zret._head) = nullptr;
1116 zret._tail = _tail;
1117
1118 _count -= n;
1119 _tail = spot;
1120 L::next_ptr(_tail) = nullptr;
1121
1122 return zret;
1123}
1124
1125template <typename L>
1126auto
1127IntrusiveDList<L>::split_suffix(unsigned int n) -> self_type {
1128 if (n <= _count) {
1129 return this->take_suffix(n);
1130 }
1131 return {};
1132}
1133
1134template <typename L>
1135auto
1136IntrusiveDList<L>::prepend(IntrusiveDList::self_type &src) -> self_type & {
1137 if (src._count > 0) {
1138 if (_count == 0) {
1139 *this = std::move(src);
1140 } else {
1141 L::prev_ptr(_head) = src._tail;
1142 L::next_ptr(src._tail) = _head;
1143 _count += src._count;
1144 _head = src._head;
1145 src.clear();
1146 }
1147 }
1148 return *this;
1149}
1150
1151template <typename L>
1152auto
1153IntrusiveDList<L>::append(IntrusiveDList::self_type &src) -> self_type & {
1154 if (src._count > 0) {
1155 if (_count == 0) {
1156 *this = std::move(src);
1157 } else {
1158 L::next_ptr(_tail) = src._head;
1159 L::prev_ptr(src._head) = _tail;
1160 _count += src._count;
1161 _tail = src._tail;
1162 src.clear();
1163 }
1164 }
1165 return *this;
1166}
1167
1168namespace detail {
1170// Make @c apply more convenient by allowing the function to take a reference type or pointer type
1171// to the container elements. The pointer type is the base, plus a shim to convert from a reference
1172// type functor to a pointer type. The complex return type definition forces only one, but
1173// not both, to be valid for a particular functor. This also must be done via free functions and not
1174// method overloads because the compiler forces a match up of method definitions and declarations
1175// before any template instantiation.
1176
1177template <typename L, typename F>
1178auto
1179Intrusive_DList_Apply(IntrusiveDList<L> &list, F &&f)
1180 -> decltype(f(*static_cast<typename IntrusiveDList<L>::value_type *>(nullptr)), list) {
1181 return list.apply([&f](typename IntrusiveDList<L>::value_type *v) { return f(*v); });
1182}
1183
1184template <typename L, typename F>
1185auto
1186Intrusive_DList_Apply(IntrusiveDList<L> &list, F &&f)
1187 -> decltype(f(static_cast<typename IntrusiveDList<L>::value_type *>(nullptr)), list) {
1188 auto spot{list.begin()};
1189 auto limit{list.end()};
1190 while (spot != limit) {
1191 f(spot++); // post increment means @a spot is updated before @a f is applied.
1192 }
1193 return list;
1194}
1195} // namespace detail
1196
1197template <typename L>
1198template <typename F>
1199auto
1200IntrusiveDList<L>::apply(F &&f) -> self_type & {
1201 return detail::Intrusive_DList_Apply(*this, f);
1202}
1204}} // namespace swoc::SWOC_VERSION_NS
bool operator==(RBNode *n, RBNode::Color c)
Definition RBTree.cc:19
For template deduction guides.
Definition ArenaWriter.cc:9
bool operator!=(IP4Addr const &lhs, IP4Addr const &rhs)
Definition IPAddr.h:845