• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 // An InlinedVector<T,N,A> is like a std::vector<T,A>, except that storage
17 // for sequences of length <= N are provided inline without requiring
18 // any heap allocation.  Typically N is very small (e.g., 4) so that
19 // sequences that are expected to be short do not require allocations.
20 //
21 // Only some of the std::vector<> operations are currently implemented.
22 // Other operations may be added as needed to facilitate migrating
23 // code that uses std::vector<> to InlinedVector<>.
24 //
25 // NOTE: If you want an inlined version to replace use of a
26 // std::vector<bool>, consider using util::bitmap::InlinedBitVector<NBITS>
27 // in util/bitmap/inlined_bitvector.h
28 //
29 // TODO(billydonahue): change size_t to size_type where appropriate.
30 
31 #ifndef TENSORFLOW_LIB_GTL_INLINED_VECTOR_H_
32 #define TENSORFLOW_LIB_GTL_INLINED_VECTOR_H_
33 
34 #include <stddef.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/types.h>
38 #include <algorithm>
39 #include <cstddef>
40 #include <iterator>
41 #include <memory>
42 #include <type_traits>
43 #include <vector>
44 
45 #include "tensorflow/core/lib/gtl/manual_constructor.h"
46 #include "tensorflow/core/platform/cpu_info.h"
47 #include "tensorflow/core/platform/logging.h"
48 #include "tensorflow/core/platform/mem.h"
49 #include "tensorflow/core/platform/types.h"
50 
51 #include <initializer_list>  // NOLINT(build/include_order)
52 
53 namespace tensorflow {
54 namespace gtl {
55 
56 template <typename T, int N>
57 class InlinedVector {
58  public:
59   typedef T value_type;
60   typedef T* pointer;
61   typedef const T* const_pointer;
62   typedef T& reference;
63   typedef const T& const_reference;
64   typedef size_t size_type;
65   typedef std::ptrdiff_t difference_type;
66   typedef pointer iterator;
67   typedef const_pointer const_iterator;
68 
69   // Create an empty vector
70   InlinedVector();
71 
72   // Create a vector with n copies of value_type().
73   explicit InlinedVector(size_t n);
74 
75   // Create a vector with n copies of elem
76   InlinedVector(size_t n, const value_type& elem);
77 
78   // Create and initialize with the elements [range_start .. range_end).
79   // The unused enable_if argument restricts this constructor so that it is
80   // elided when value_type is an integral type.  This prevents ambiguous
81   // interpretation between a call to this constructor with two integral
82   // arguments and a call to the preceding (n, elem) constructor.
83   template <typename InputIterator>
84   InlinedVector(
85       InputIterator range_start, InputIterator range_end,
86       typename std::enable_if<!std::is_integral<InputIterator>::value>::type* =
87           NULL) {
88     InitRep();
89     AppendRange(range_start, range_end);
90   }
91 
InlinedVector(std::initializer_list<value_type> init)92   InlinedVector(std::initializer_list<value_type> init) {
93     InitRep();
94     AppendRange(init.begin(), init.end());
95   }
96 
97   InlinedVector(const InlinedVector& v);
98 
~InlinedVector()99   ~InlinedVector() { clear(); }
100 
101   InlinedVector& operator=(const InlinedVector& v) {
102     // Optimized to avoid reallocation.
103     // Prefer reassignment to copy construction for elements.
104     const size_t s = size();
105     const size_t vs = v.size();
106     if (s < vs) {  // grow
107       reserve(vs);
108       if (s) std::copy(v.begin(), v.begin() + s, begin());
109       std::copy(v.begin() + s, v.end(), std::back_inserter(*this));
110     } else {  // maybe shrink
111       erase(begin() + vs, end());
112       std::copy(v.begin(), v.end(), begin());
113     }
114     return *this;
115   }
116 
size()117   size_t size() const { return size_internal(); }
118 
empty()119   bool empty() const { return (size() == 0); }
120 
121   // Return number of elements that can be stored in vector
122   // without requiring a reallocation of underlying memory
capacity()123   size_t capacity() const {
124     if (is_inline()) {
125       return kFit;
126     } else {
127       return static_cast<size_t>(1) << u_.data[kSize - 2];
128     }
129   }
130 
131   // Return a pointer to the underlying array.
132   // Only result[0,size()-1] are defined.
data()133   pointer data() {
134     if (is_inline()) {
135       return reinterpret_cast<T*>(u_.data);
136     } else {
137       return outofline_pointer();
138     }
139   }
data()140   const_pointer data() const {
141     return const_cast<InlinedVector<T, N>*>(this)->data();
142   }
143 
144   // Remove all elements
clear()145   void clear() {
146     DiscardStorage();
147     u_.data[kSize - 1] = 0;
148   }
149 
150   // Return the ith element
151   // REQUIRES: 0 <= i < size()
at(size_t i)152   const value_type& at(size_t i) const {
153     DCHECK_LT(i, size());
154     return data()[i];
155   }
156   const value_type& operator[](size_t i) const {
157     DCHECK_LT(i, size());
158     return data()[i];
159   }
160 
161   // Return a non-const reference to the ith element
162   // REQUIRES: 0 <= i < size()
at(size_t i)163   value_type& at(size_t i) {
164     DCHECK_LT(i, size());
165     return data()[i];
166   }
167   value_type& operator[](size_t i) {
168     DCHECK_LT(i, size());
169     return data()[i];
170   }
171 
back()172   value_type& back() {
173     DCHECK(!empty());
174     return at(size() - 1);
175   }
176 
back()177   const value_type& back() const {
178     DCHECK(!empty());
179     return at(size() - 1);
180   }
181 
front()182   value_type& front() {
183     DCHECK(!empty());
184     return at(0);
185   }
186 
front()187   const value_type& front() const {
188     DCHECK(!empty());
189     return at(0);
190   }
191 
192   // Append a T constructed with args to the vector.
193   // Increases size() by one.
194   // Amortized complexity: O(1)
195   // Worst-case complexity: O(size())
196   template <typename... Args>
emplace_back(Args &&...args)197   void emplace_back(Args&&... args) {
198     size_t s = size();
199     DCHECK_LE(s, capacity());
200     if (s < capacity()) {
201       new (data() + s) T(std::forward<Args>(args)...);
202       set_size_internal(s + 1);
203     } else {
204       EmplaceBackSlow(std::forward<Args>(args)...);
205     }
206   }
207 
208   // Append t to the vector.
209   // Increases size() by one.
210   // Amortized complexity: O(1)
211   // Worst-case complexity: O(size())
push_back(const value_type & t)212   void push_back(const value_type& t) { emplace_back(t); }
push_back(value_type && t)213   void push_back(value_type&& t) { emplace_back(std::move(t)); }
214 
pop_back()215   inline void pop_back() {
216     DCHECK(!empty());
217     const size_t s = size();
218     Destroy(data() + s - 1, 1);
219     set_size_internal(s - 1);
220   }
221 
222   // Resizes the vector to contain "n" elements.
223   // If "n" is smaller than the initial size, extra elements are destroyed.
224   // If "n" is larger than the initial size, enough copies of "elem"
225   // are appended to increase the size to "n". If "elem" is omitted,
226   // new elements are value-initialized.
resize(size_t n)227   void resize(size_t n) { Resize<ValueInit>(n, nullptr); }
resize(size_t n,const value_type & elem)228   void resize(size_t n, const value_type& elem) { Resize<Fill>(n, &elem); }
229 
begin()230   iterator begin() { return data(); }
begin()231   const_iterator begin() const { return data(); }
232 
end()233   iterator end() { return data() + size(); }
end()234   const_iterator end() const { return data() + size(); }
235 
236   iterator insert(iterator pos, const value_type& v);
237 
erase(iterator pos)238   iterator erase(iterator pos) {
239     DCHECK_LT(pos, end());
240     DCHECK_GE(pos, begin());
241     std::copy(pos + 1, end(), pos);
242     pop_back();
243     return pos;
244   }
245 
246   iterator erase(iterator first, iterator last);
247 
248   // Enlarges the underlying representation so it can hold at least
249   // "n" elements without reallocation.
250   // Does not change size() or the actual contents of the vector.
reserve(size_t n)251   void reserve(size_t n) {
252     if (n > capacity()) {
253       // Make room for new elements
254       Grow<Move>(n);
255     }
256   }
257 
258   // Swap the contents of *this with other.
259   // REQUIRES: value_type is swappable and copyable.
260   void swap(InlinedVector& other);
261 
262  private:
263   // Representation can either be inlined or out-of-line.
264   // In either case, at least sizeof(void*) + 8 bytes are available.
265   //
266   // Inlined:
267   //   Last byte holds the length.
268   //   First (length*sizeof(T)) bytes stores the elements.
269   // Outlined:
270   //   Last byte holds kSentinel.
271   //   Second-last byte holds lg(capacity)
272   //   Preceding 6 bytes hold size.
273   //   First sizeof(T*) bytes hold pointer.
274 
275   // Compute rep size.
276   static const size_t kSizeUnaligned = N * sizeof(T) + 1;  // Room for tag
277   static const size_t kSize = ((kSizeUnaligned + 15) / 16) * 16;  // Align
278 
279   // See how many fit T we can fit inside kSize, but no more than 254
280   // since 255 is used as sentinel tag for out-of-line allocation.
281   static const unsigned int kSentinel = 255;
282   static const size_t kFit1 = (kSize - 1) / sizeof(T);
283   static const size_t kFit = (kFit1 >= kSentinel) ? (kSentinel - 1) : kFit1;
284 
285   union {
286     unsigned char data[kSize];
287     // Force data to be aligned enough for a pointer.
288     T* unused_aligner;
289   } u_;
290 
InitRep()291   inline void InitRep() { u_.data[kSize - 1] = 0; }
is_inline()292   inline bool is_inline() const { return u_.data[kSize - 1] != kSentinel; }
293 
outofline_pointer()294   inline T* outofline_pointer() const {
295     T* ptr;
296     memcpy(&ptr, &u_.data[0], sizeof(ptr));
297     return ptr;
298   }
299 
set_outofline_pointer(T * p)300   inline void set_outofline_pointer(T* p) {
301     memcpy(&u_.data[0], &p, sizeof(p));
302   }
303 
outofline_word()304   inline uint64_t outofline_word() const {
305     uint64_t word;
306     memcpy(&word, &u_.data[kSize - 8], sizeof(word));
307     return word;
308   }
309 
set_outofline_word(uint64_t w)310   inline void set_outofline_word(uint64_t w) {
311     memcpy(&u_.data[kSize - 8], &w, sizeof(w));
312   }
313 
size_internal()314   inline size_t size_internal() const {
315     uint8_t s = static_cast<uint8_t>(u_.data[kSize - 1]);
316     if (s != kSentinel) {
317       return static_cast<size_t>(s);
318     } else {
319       const uint64_t word = outofline_word();
320       if (port::kLittleEndian) {
321         // The sentinel and capacity bits are most-significant bits in word.
322         return static_cast<size_t>(word & 0xffffffffffffull);
323       } else {
324         // The sentinel and capacity bits are least-significant bits in word.
325         return static_cast<size_t>(word >> 16);
326       }
327     }
328   }
329 
set_size_internal(size_t n)330   void set_size_internal(size_t n) {
331     if (is_inline()) {
332       DCHECK_LT(n, kSentinel);
333       u_.data[kSize - 1] = static_cast<unsigned char>(n);
334     } else {
335       uint64_t word;
336       if (port::kLittleEndian) {
337         // The sentinel and capacity bits are most-significant bits in word.
338         word = (static_cast<uint64_t>(n) |
339                 (static_cast<uint64_t>(u_.data[kSize - 2]) << 48) |
340                 (static_cast<uint64_t>(kSentinel) << 56));
341       } else {
342         // The sentinel and capacity bits are least-significant bits in word.
343         word = ((static_cast<uint64_t>(n) << 16) |
344                 (static_cast<uint64_t>(u_.data[kSize - 2]) << 8) |
345                 (static_cast<uint64_t>(kSentinel)));
346       }
347       set_outofline_word(word);
348       DCHECK_EQ(u_.data[kSize - 1], kSentinel) << n;
349     }
350   }
351 
DiscardStorage()352   void DiscardStorage() {
353     T* base = data();
354     size_t n = size();
355     Destroy(base, n);
356     if (!is_inline()) {
357       port::Free(base);
358     }
359   }
360 
361   template <typename... Args>
EmplaceBackSlow(Args &&...args)362   void EmplaceBackSlow(Args&&... args) {
363     const size_t s = size();
364     DCHECK_EQ(s, capacity());
365     Grow<Move, Construct>(s + 1, std::forward<Args>(args)...);
366     set_size_internal(s + 1);
367   }
368 
369   // Movers for Grow
370   // Does nothing.
Nop(T * src,size_t n,T * dst)371   static void Nop(T* src, size_t n, T* dst) {}
372 
373   // Moves srcs[0,n-1] contents to dst[0,n-1].
Move(T * src,size_t n,T * dst)374   static void Move(T* src, size_t n, T* dst) {
375     for (size_t i = 0; i < n; i++) {
376       new (dst + i) T(std::move(*(src + i)));
377     }
378   }
379 
380   // Initializers for Resize.
381   // Initializes dst[0,n-1] with empty constructor.
ValueInit(const T *,size_t n,T * dst)382   static void ValueInit(const T*, size_t n, T* dst) {
383     for (size_t i = 0; i < n; i++) {
384       new (dst + i) T();
385     }
386   }
387 
388   // Initializes dst[0,n-1] with copies of *src.
Fill(const T * src,size_t n,T * dst)389   static void Fill(const T* src, size_t n, T* dst) {
390     for (size_t i = 0; i < n; i++) {
391       new (dst + i) T(*src);
392     }
393   }
394 
Destroy(T * src,int n)395   void Destroy(T* src, int n) {
396     if (!std::is_trivially_destructible<T>::value) {
397       for (int i = 0; i < n; i++) {
398         (src + i)->~T();
399       }
400     }
401   }
402 
403   // Initialization methods for Grow.
404   // 1) Leave uninitialized memory.
405   struct Uninitialized {
operatorUninitialized406     void operator()(T*) const {}
407   };
408   // 2) Construct a T with args at not-yet-initialized memory pointed by dst.
409   struct Construct {
410     template <class... Args>
operatorConstruct411     void operator()(T* dst, Args&&... args) const {
412       new (dst) T(std::forward<Args>(args)...);
413     }
414   };
415 
416   // Grow so that capacity >= n.  Uses Mover to move existing elements
417   // to new buffer, and possibly initialize the new element according
418   // to InitType.
419   // We pass the InitType and Mover as template arguments so that
420   // this code compiles even if T does not support copying or default
421   // construction.
422   template <void(Mover)(T*, size_t, T*), class InitType = Uninitialized,
423             class... Args>
Grow(size_t n,Args &&...args)424   void Grow(size_t n, Args&&... args) {
425     size_t s = size();
426     DCHECK_LE(s, capacity());
427 
428     // Compute new capacity by repeatedly doubling current capacity
429     size_t target = 1;
430     size_t target_lg = 0;
431     while (target < kFit || target < n) {
432       // TODO(psrc): Check and avoid overflow?
433       target_lg++;
434       target <<= 1;
435     }
436 
437     T* src = data();
438     T* dst = static_cast<T*>(port::Malloc(target * sizeof(T)));
439 
440     // Need to copy elem before discarding src since it might alias src.
441     InitType{}(dst + s, std::forward<Args>(args)...);
442     Mover(src, s, dst);
443     DiscardStorage();
444 
445     u_.data[kSize - 1] = kSentinel;
446     u_.data[kSize - 2] = static_cast<unsigned char>(target_lg);
447     set_size_internal(s);
448     DCHECK_EQ(capacity(), target);
449     set_outofline_pointer(dst);
450   }
451 
452   // Resize to size n.  Any new elements are initialized by passing
453   // elem and the destination to Initializer.  We pass the Initializer
454   // as a template argument so that this code compiles even if T does
455   // not support copying.
456   template <void(Initializer)(const T*, size_t, T*)>
Resize(size_t n,const T * elem)457   void Resize(size_t n, const T* elem) {
458     size_t s = size();
459     if (n <= s) {
460       Destroy(data() + n, s - n);
461       set_size_internal(n);
462       return;
463     }
464     reserve(n);
465     DCHECK_GE(capacity(), n);
466     set_size_internal(n);
467     Initializer(elem, n - s, data() + s);
468   }
469 
470   template <typename Iter>
471   void AppendRange(Iter first, Iter last, std::input_iterator_tag);
472 
473   // Faster path for forward iterators.
474   template <typename Iter>
475   void AppendRange(Iter first, Iter last, std::forward_iterator_tag);
476 
477   template <typename Iter>
478   void AppendRange(Iter first, Iter last);
479 };
480 
481 // Provide linkage for constants.
482 template <typename T, int N>
483 const size_t InlinedVector<T, N>::kSizeUnaligned;
484 template <typename T, int N>
485 const size_t InlinedVector<T, N>::kSize;
486 template <typename T, int N>
487 const unsigned int InlinedVector<T, N>::kSentinel;
488 template <typename T, int N>
489 const size_t InlinedVector<T, N>::kFit1;
490 template <typename T, int N>
491 const size_t InlinedVector<T, N>::kFit;
492 
493 template <typename T, int N>
swap(InlinedVector<T,N> & a,InlinedVector<T,N> & b)494 inline void swap(InlinedVector<T, N>& a, InlinedVector<T, N>& b) {
495   a.swap(b);
496 }
497 
498 template <typename T, int N>
499 inline bool operator==(const InlinedVector<T, N>& a,
500                        const InlinedVector<T, N>& b) {
501   return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
502 }
503 
504 template <typename T, int N>
505 inline bool operator!=(const InlinedVector<T, N>& a,
506                        const InlinedVector<T, N>& b) {
507   return !(a == b);
508 }
509 
510 template <typename T, int N>
511 inline bool operator<(const InlinedVector<T, N>& a,
512                       const InlinedVector<T, N>& b) {
513   return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
514 }
515 
516 template <typename T, int N>
517 inline bool operator>(const InlinedVector<T, N>& a,
518                       const InlinedVector<T, N>& b) {
519   return b < a;
520 }
521 
522 template <typename T, int N>
523 inline bool operator<=(const InlinedVector<T, N>& a,
524                        const InlinedVector<T, N>& b) {
525   return !(b < a);
526 }
527 
528 template <typename T, int N>
529 inline bool operator>=(const InlinedVector<T, N>& a,
530                        const InlinedVector<T, N>& b) {
531   return !(a < b);
532 }
533 
534 // ========================================
535 // Implementation
536 
537 template <typename T, int N>
InlinedVector()538 inline InlinedVector<T, N>::InlinedVector() {
539   InitRep();
540 }
541 
542 template <typename T, int N>
InlinedVector(size_t n)543 inline InlinedVector<T, N>::InlinedVector(size_t n) {
544   InitRep();
545   if (n > capacity()) {
546     Grow<Nop>(n);  // Must use Nop in case T is not copyable
547   }
548   set_size_internal(n);
549   ValueInit(nullptr, n, data());
550 }
551 
552 template <typename T, int N>
InlinedVector(size_t n,const value_type & elem)553 inline InlinedVector<T, N>::InlinedVector(size_t n, const value_type& elem) {
554   InitRep();
555   if (n > capacity()) {
556     Grow<Nop>(n);  // Can use Nop since we know we have nothing to copy
557   }
558   set_size_internal(n);
559   Fill(&elem, n, data());
560 }
561 
562 template <typename T, int N>
InlinedVector(const InlinedVector & v)563 inline InlinedVector<T, N>::InlinedVector(const InlinedVector& v) {
564   InitRep();
565   *this = v;
566 }
567 
568 template <typename T, int N>
insert(iterator pos,const value_type & v)569 typename InlinedVector<T, N>::iterator InlinedVector<T, N>::insert(
570     iterator pos, const value_type& v) {
571   DCHECK_GE(pos, begin());
572   DCHECK_LE(pos, end());
573   if (pos == end()) {
574     push_back(v);
575     return end() - 1;
576   }
577   size_t s = size();
578   size_t idx = std::distance(begin(), pos);
579   if (s == capacity()) {
580     Grow<Move>(s + 1);
581   }
582   CHECK_LT(s, capacity());
583   pos = begin() + idx;  // Reset 'pos' into a post-enlarge iterator.
584   Fill(data() + s - 1, 1, data() + s);  // data[s] = data[s-1]
585   std::copy_backward(pos, data() + s - 1, data() + s);
586   *pos = v;
587 
588   set_size_internal(s + 1);
589   return pos;
590 }
591 
592 template <typename T, int N>
erase(iterator first,iterator last)593 typename InlinedVector<T, N>::iterator InlinedVector<T, N>::erase(
594     iterator first, iterator last) {
595   DCHECK_LE(begin(), first);
596   DCHECK_LE(first, last);
597   DCHECK_LE(last, end());
598 
599   size_t s = size();
600   ptrdiff_t erase_gap = std::distance(first, last);
601   std::copy(last, data() + s, first);
602   Destroy(data() + s - erase_gap, erase_gap);
603   set_size_internal(s - erase_gap);
604   return first;
605 }
606 
607 template <typename T, int N>
swap(InlinedVector & other)608 void InlinedVector<T, N>::swap(InlinedVector& other) {
609   using std::swap;  // Augment ADL with std::swap.
610   if (&other == this) {
611     return;
612   }
613 
614   InlinedVector* a = this;
615   InlinedVector* b = &other;
616 
617   const bool a_inline = a->is_inline();
618   const bool b_inline = b->is_inline();
619 
620   if (!a_inline && !b_inline) {
621     // Just swap the top-level representations.
622     T* aptr = a->outofline_pointer();
623     T* bptr = b->outofline_pointer();
624     a->set_outofline_pointer(bptr);
625     b->set_outofline_pointer(aptr);
626 
627     uint64_t aword = a->outofline_word();
628     uint64_t bword = b->outofline_word();
629     a->set_outofline_word(bword);
630     b->set_outofline_word(aword);
631     return;
632   }
633 
634   // Make a the larger of the two to reduce number of cases.
635   size_t a_size = a->size();
636   size_t b_size = b->size();
637   if (a->size() < b->size()) {
638     swap(a, b);
639     swap(a_size, b_size);
640   }
641   DCHECK_GE(a_size, b_size);
642 
643   if (b->capacity() < a_size) {
644     b->Grow<Move>(a_size);
645   }
646 
647   // One is inline and one is not.
648   // 'a' is larger. Swap the elements up to the smaller array size.
649   std::swap_ranges(a->data(), a->data() + b_size, b->data());
650   std::uninitialized_copy(a->data() + b_size, a->data() + a_size,
651                           b->data() + b_size);
652   Destroy(a->data() + b_size, a_size - b_size);
653   a->set_size_internal(b_size);
654   b->set_size_internal(a_size);
655   DCHECK_EQ(b->size(), a_size);
656   DCHECK_EQ(a->size(), b_size);
657 }
658 
659 template <typename T, int N>
660 template <typename Iter>
AppendRange(Iter first,Iter last,std::input_iterator_tag)661 inline void InlinedVector<T, N>::AppendRange(Iter first, Iter last,
662                                              std::input_iterator_tag) {
663   std::copy(first, last, std::back_inserter(*this));
664 }
665 
666 template <typename T, int N>
667 template <typename Iter>
AppendRange(Iter first,Iter last,std::forward_iterator_tag)668 inline void InlinedVector<T, N>::AppendRange(Iter first, Iter last,
669                                              std::forward_iterator_tag) {
670   typedef typename std::iterator_traits<Iter>::difference_type Length;
671   Length length = std::distance(first, last);
672   size_t s = size();
673   reserve(s + length);
674   std::uninitialized_copy_n(first, length, data() + s);
675   set_size_internal(s + length);
676 }
677 
678 template <typename T, int N>
679 template <typename Iter>
AppendRange(Iter first,Iter last)680 inline void InlinedVector<T, N>::AppendRange(Iter first, Iter last) {
681   typedef typename std::iterator_traits<Iter>::iterator_category IterTag;
682   AppendRange(first, last, IterTag());
683 }
684 
685 }  // namespace gtl
686 }  // namespace tensorflow
687 
688 #endif  // TENSORFLOW_LIB_GTL_INLINED_VECTOR_H_
689