Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
HashTable.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Containers_DataStructures_HashTable_h_
5#define _Stroika_Foundation_Containers_DataStructures_HashTable_h_
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include "Stroika/Foundation/Common/Common.h"
11#include "Stroika/Foundation/Common/KeyValuePair.h"
12#include "Stroika/Foundation/Containers/Common.h"
13#include "Stroika/Foundation/Cryptography/Digest/HashBase.h"
17
18/**
19 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
20 */
21
23
25
26 /**
27 * HashTable module design notes:
28 * o use traits to pick between separate chaining, and linear probing (do sep chaining first since easiest)
29 * o Sometimes want HashTable<T> and sometimes HashTable<KEY,MAPPED_VALUE> - later appears more natural due to
30 * ability to conditionally create overloads of things like Add (KEY) (when mapped_value=void).
31 */
32 namespace HashTable_Support {
33
34 /**
35 * \brief used internally to select HashTable implementation strategies. Callers dont use directly, but use SeparateChainingOptions<>
36 */
38
39 /**
40 * \brief used internally to select HashTable implementation strategies. NOT YET IMPLEMENTED OR THOUGHT OUT (see https://en.wikipedia.org/wiki/Linear_probing) - tricky part is delete
41 */
43
44 /**
45 * \brief use as LAYOUT_OPTIONS for HashTable DefaultTraits<> template
46 */
47 template <typename KEY_TYPE, typename MAPPED_TYPE, size_t INLINE_ELTS_PER_CHAIN = 2, size_t INLINE_BUCKETS = 5>
49 static constexpr size_t kBufferedElementsPerChain = INLINE_ELTS_PER_CHAIN;
50 static constexpr size_t kBufferedBuckets = INLINE_BUCKETS;
51 };
52
53 /**
54 * KEY_TYPE the type of the key element stored in the SkipList.
55 * KEY_COMPARER is nearly always as given
56 * AddOrExtendOrReplaceMode addOrExtendOrReplace defaults to eAddExtras, but here the caller may not want the default. There is no good default here.
57 * ALTERNATE_FIND_TYPE can often be omitted (default) - but allows Find () to be overloaded (argument comparer) on a different type (besides just KEY_TYPE).
58 */
59 template <typename KEY_TYPE, typename MAPPED_TYPE, Cryptography::Digest::IHashFunction<KEY_TYPE> HASHER = hash<KEY_TYPE>,
60 Common::IEqualsComparer<KEY_TYPE> EQUALS_COMPARER = equal_to<KEY_TYPE>, typename LAYOUT_OPTIONS = SeparateChainingOptions<KEY_TYPE, MAPPED_TYPE>,
61 AddOrExtendOrReplaceMode addOrExtendOrReplace = AddOrExtendOrReplaceMode::eAddExtras, typename ALTERNATE_FIND_TYPE = void>
63 /**
64 */
65 using key_type = KEY_TYPE;
66
67 /**
68 */
69 using mapped_type = MAPPED_TYPE;
70
71 /**
72 */
74
75 /**
76 */
77 using KeyHasherType = HASHER;
78
79 /**
80 */
81 using KeyEqualsComparerType = EQUALS_COMPARER;
82
83 /**
84 * separate chaining (for now) - or some probing variation
85 */
86 using LayoutType = LAYOUT_OPTIONS;
87
88 /**
89 * like is_transparent mechanism in C++14, except just adds one type (if not void) to the set of types you can find looking for)
90 * \note when using AlternateFindType != void, caller must ALSO provide a compare function which accepts combinations of T, and AlternateFindType
91 */
92 using AlternateFindType = ALTERNATE_FIND_TYPE;
93
94 /**
95 * \see AddOrExtendOrReplaceMode
96 */
97 static constexpr AddOrExtendOrReplaceMode kAddOrExtendOrReplace = addOrExtendOrReplace;
98
99 /**
100 */
101 static constexpr bool kAutoShrinkBucketCount = false;
102 };
103
104 /**
105 * \brief validated the HashTable provided TRAITS object looks healthy (for better compiler diagnostics and usage docs)
106 */
107 template <typename TRAITS, typename KEY_TYPE, typename MAPPED_TYPE>
108 concept IValidTraits = requires (TRAITS traits) {
109 { declval<typename TRAITS::KeyHasherType> () } -> Cryptography::Digest::IHashFunction<KEY_TYPE>;
110 { declval<typename TRAITS::KeyEqualsComparerType> () } -> Common::IEqualsComparer<KEY_TYPE>;
111 { declval<typename TRAITS::LayoutType> () };
112 { TRAITS::kAddOrExtendOrReplace } -> convertible_to<AddOrExtendOrReplaceMode>;
113 { TRAITS::kAutoShrinkBucketCount } -> convertible_to<bool>;
114 } and (same_as<typename TRAITS::AlternateFindType, void> or requires (TRAITS traits) {
115 { declval<typename TRAITS::AlternateFindType> () };
116 {
117 declval<typename TRAITS::KeyHasherType> ()
119#if 0
120 // @todo get this working!
121 { declval<typename TRAITS::KeyEqualsComparerType>() } -> Common::IEqualsComparer<typename TRAITS::AlternateFindType>;
122#endif
123 });
124
125 }
126
127 /**
128 * \brief implement hash table support in a lightweight standard template library style. Use
129 * traits to describe various choices about hashtable layout (separate chaining vs linear probing) etc
130 */
131 template <typename KEY_TYPE, typename MAPPED_TYPE = void, HashTable_Support::IValidTraits<KEY_TYPE, MAPPED_TYPE> TRAITS = HashTable_Support::DefaultTraits<KEY_TYPE, MAPPED_TYPE>>
133 public:
134 /**
135 */
136 using key_type = typename TRAITS::key_type;
137
138 public:
139 /**
140 */
141 using mapped_type = typename TRAITS::mapped_type;
142
143 public:
144 /**
145 */
146 using value_type = typename TRAITS::value_type;
147
148 public:
149 /**
150 */
151 using KeyHasherType = typename TRAITS::KeyHasherType;
152
153 public:
154 /**
155 */
156 using KeyEqualsComparerType = typename TRAITS::KeyEqualsComparerType;
157
158 public:
159 /**
160 * Basic (mostly internal) element used by ForwardIterator. Abstract name so can be referenced generically across 'DataStructure' objects
161 */
162 using UnderlyingIteratorRep = tuple<size_t, size_t>;
163
164 public:
165 /**
166 */
167 using TraitsType = TRAITS;
168
169 public:
170 /**
171 * \note HashTable (const HashTable& src) (and move ctor) guarantee elements iteration order unchanged in copy
172 * (see https://stackoverflow.com/questions/79551148/how-to-copy-stdunordered-map-while-preserving-its-order?noredirect=1)
173 */
174 HashTable (const KeyHasherType& hashFunction = {}, const KeyEqualsComparerType& keyComparer = {});
175 HashTable (size_t bucketCount, const KeyHasherType& hashFunction = {}, const KeyEqualsComparerType& keyComparer = {});
176 HashTable (HashTable&& src) noexcept = default;
177 HashTable (const HashTable& src) = default;
178 ~HashTable () = default;
179
180 public:
181 /**
182 */
183 nonvirtual HashTable& operator= (const HashTable&) = default;
184 nonvirtual HashTable& operator= (HashTable&&) = default;
185
186 public:
187 /**
188 * \note this name mimics the name used in https://en.cppreference.com/w/cpp/container/unordered_set/hash_function
189 */
190 nonvirtual KeyHasherType hash_function () const;
191
192 public:
193 /**
194 * \note this name mimics the naming used in https://en.cppreference.com/w/cpp/container/unordered_map/key_eq
195 *
196 * GetKeyEqualsComparerType
197 */
198 nonvirtual KeyEqualsComparerType key_eq () const;
199
200 public:
201 class ForwardIterator;
202
203 public:
204 /**
205 */
206 nonvirtual ForwardIterator begin ();
207
208 public:
209 /**
210 */
211 constexpr static ForwardIterator end ();
212
213 public:
214 /*
215 * Support for COW (CopyOnWrite):
216 *
217 * Take iterator 'pi' which is originally a valid iterator from 'movedFrom' - and replace *pi with a valid
218 * iterator from 'this' - which points at the same logical position. This requires that this container
219 * was just 'copied' from 'movedFrom' - and is used to produce an equivalent iterator (since iterators are tied to
220 * the container they were iterating over).
221 */
222 nonvirtual void MoveIteratorHereAfterClone (ForwardIterator* pi, const HashTable* movedFrom) const;
223
224 public:
225 /**
226 * \brief Add an item (key value pair typically, but the value can be void). Return true on list change; Respects TRAITS::kAddOrExtendOrReplace
227 *
228 * If you add different values with the same key, but it is unspecified which item will be returned on subsequent Find or Remove (key) calls.
229 *
230 * Returns true if the list was changed (if eAddReplaces, and key found, return true even if val same as value already there because we cannot generically compare values)
231 *
232 * \note Behavior of adding redundant keys (keys which are already present) depends on TRAITS::kAddOrExtendOrReplace.
233 *
234 * returns true iff container modified by this operation (so for add replaces mode no info if already was present)
235 *
236 * \note Runtime performance/complexity: ??
237 * Average: O(1)
238 * Worst: N
239 */
240 nonvirtual bool Add (const value_type& t);
241 nonvirtual bool Add (const key_type& t)
242 requires (same_as<MAPPED_TYPE, void>);
243 template <same_as<MAPPED_TYPE> MAPPED_TYPE2 = MAPPED_TYPE>
244 nonvirtual bool Add (const key_type& t, const MAPPED_TYPE2& m)
245 requires (not same_as<MAPPED_TYPE, void>);
246
247 public:
248 /**
249 * \brief somewhat stdlib-like names - that will do what is expected of someone from stdc++, except for the
250 * lack of return type that wouldn't make sense here since Stroika iterators not directly used for modification.
251 *
252 * \see https://en.cppreference.com/w/cpp/container/unordered_map/insert
253 */
254 nonvirtual void insert (const pair<KEY_TYPE, MAPPED_TYPE>& p);
255
256 public:
257 /**
258 */
259 nonvirtual optional<value_type> Lookup (const key_type& t);
260
261 public:
262 /**
263 * \req t present - use RemoveIf() to avoid that precondition
264 */
265 nonvirtual void Remove (const ForwardIterator& i, ForwardIterator* nextI = nullptr);
266 nonvirtual void Remove (const key_type& t);
267
268 public:
269 /**
270 */
271 nonvirtual bool RemoveIf (const key_type& t);
272
273 public:
274 /**
275 * \brief stdlib like names and semantics (though may want to rethink the ForwardIterator vs UnderlyingIteratorRep thing)
276 */
277 nonvirtual ForwardIterator erase (const ForwardIterator& i);
279
280 public:
281 /**
282 * TBD... NOT same as https://en.cppreference.com/w/cpp/container/unordered_set/rehash
283 *
284 * Use ROUGHLY the argument number of hash buckets. Call bucket_count() to find number actually used.
285 *
286 * bucket_count never goes below 1, but if you request a number too low, just goes to lowest allowed.
287 * so ReHash(0) can be used to 'compact' as much as possible.
288 */
289 nonvirtual void ReHash (size_t newBucketCount);
290
291 public:
292 /**
293 * This examines load_factor and max_load_factor(), and depending on relationship, makes
294 * a guess as to best size to use in call to ReHash();
295 */
296 nonvirtual void ReHashIfNeeded ();
297
298 public:
299 /**
300 * \note Runtime performance/complexity:
301 * Always: constant
302 */
303 nonvirtual size_t bucket_count () const;
304
305 public:
306 /**
307 * \note Runtime performance/complexity:
308 * Always: constant
309 */
310 nonvirtual size_t bucket_size (size_t bucketIdx) const;
311
312 public:
313 /**
314 * \note Runtime performance/complexity:
315 * Always: constant
316 */
317 nonvirtual size_t size () const;
318
319 public:
320 /**
321 * \note Runtime performance/complexity:
322 * Always: constant
323 */
324 nonvirtual bool empty () const;
325
326 public:
327 /**
328 * \brief average number of elements per bucket
329 *
330 * \see https://en.cppreference.com/w/cpp/container/unordered_set/load_factor
331 */
332 nonvirtual float load_factor () const;
333
334 public:
335 /**
336 * \brief average number of elements per bucket
337 *
338 * \see https://en.cppreference.com/w/cpp/container/unordered_set/load_factor
339 */
340 nonvirtual float max_load_factor () const;
341 nonvirtual void max_load_factor (float mlf);
342
343 public:
344 /**
345 * \note Runtime performance/complexity:
346 * Worst Case: O(N)
347 * Typical: O(N) if ! trivial_t<T> and if trivial_t, O(N-BUCKETS)
348 */
349 nonvirtual void clear ();
350
351 public:
352 /**
353 * \see https://en.cppreference.com/w/cpp/container/map/contains
354 *
355 * \note Runtime performance/complexity: ??
356 * Average: log(N)
357 * Worst: N
358 */
359 nonvirtual bool contains (ArgByValueType<key_type> key) const;
360
361 public:
362 /**
363 * \note Runtime performance/complexity:
364 * Always: O(N)
365 */
366 template <invocable<typename TRAITS::value_type> FUNCTION>
367 nonvirtual void Apply (FUNCTION&& doToElement, Execution::SequencePolicy seq = Execution::SequencePolicy::eDEFAULT) const;
368
369 public:
370 /**
371 * \note Runtime performance/complexity:
372 * overload: (key_type)
373 * Average/Worst: O(1) ; N
374 * \note Runtime performance/complexity:
375 * overload: (FUNCTION&& f overload)
376 * Average/Worst: O(N)
377 *
378 * \see also find()
379 *
380 * \note this is kind of like set<T>::find () - but not exactly, and find() doesn't really have a uniform API across the various stl containers...
381 * which is why we use Find(), instead of find() as a name
382 */
383 nonvirtual ForwardIterator Find (ArgByValueType<key_type> key) const;
384 template <typename ARG_T = typename TRAITS::AlternateFindType>
385 nonvirtual ForwardIterator Find (ARG_T key) const
386 requires (not same_as<typename TRAITS::AlternateFindType, void> and same_as<remove_cvref_t<ARG_T>, typename TRAITS::AlternateFindType>);
387 template <predicate<typename TRAITS::key_type> FUNCTION>
388 nonvirtual ForwardIterator Find (FUNCTION&& firstThat) const;
389
390 public:
391 /**
392 * \brief stdlib-ish API for 'Find' - returns iterator for found object in hashtable
393 *
394 * \note Runtime performance/complexity:
395 * overload: (key_type)
396 * Average/Worst: O(1) ; N
397 *
398 * \note closely resembles https://en.cppreference.com/w/cpp/container/unordered_set/find API, so use same name (except for const/non-const part).
399 */
400 nonvirtual ForwardIterator find (ArgByValueType<key_type> key) const;
401 template <typename ARG_T = typename TRAITS::AlternateFindType>
402 nonvirtual ForwardIterator find (ARG_T key) const
403 requires (not same_as<typename TRAITS::AlternateFindType, void> and same_as<remove_cvref_t<ARG_T>, typename TRAITS::AlternateFindType>);
404
405 public:
406 /**
407 * \note - unlike other modifying operations, this doesn't invalidate any iterators (including the argument iterator).
408 */
409 template <typename CHECKED_T = MAPPED_TYPE>
410 nonvirtual void Update (const ForwardIterator& it, ArgByValueType<CHECKED_T> newValue)
411 requires (not same_as<MAPPED_TYPE, void>);
412
413 public:
414 constexpr void Invariant () const noexcept;
415
416#if qStroika_Foundation_Debug_AssertionsChecked
417 private:
418 nonvirtual void Invariant_ () const noexcept;
419#endif
420
421 private:
422 using LayoutType_ = typename TraitsType::LayoutType;
423 static constexpr size_t kBufferedElementsPerChain_ = LayoutType_::kBufferedElementsPerChain;
424 static constexpr size_t kBufferedBuckets_ = LayoutType_::kBufferedBuckets;
425
426 // this type depends MORE INTIMATELY on LayoutType (use concepts to select when we support more)
427 //
428 struct BucketType_ {
429 // because we keep number of elements in a bucket low, often best to use array instead of linked list (performance)
431 };
432
434
435 [[no_unique_address]] KeyHasherType fHasher_;
436 [[no_unique_address]] KeyEqualsComparerType fKeyComparer_;
437
438 size_t fCachedSize_{0};
439
440 private:
441 nonvirtual size_t Hash_ (const key_type& v) const
442 {
443 Require (fBuckets_.size () > 0);
444 return fHasher_ (v) % fBuckets_.size ();
445 }
446 template <typename AT = typename TRAITS::AlternateFindType>
447 requires (not same_as<AT, void>)
448 nonvirtual size_t Hash_ (const AT& v) const
449 {
450 Require (fBuckets_.size () > 0);
451 return fHasher_ (v) % fBuckets_.size ();
452 }
453
454 private:
455 // From https://en.cppreference.com/w/cpp/container/unordered_set/unordered_set - max_load_factor = 1 by default - not wedded to that, but not a crazy default...
456 float fMaxLoadFactor_{1.0};
457 };
458
459 /**
460 * ForwardIterator allows you to iterate over a HashTable<KEY_TYPE, MAPPED_TYPE, TRAITS>. It is not safe to use a ForwardIterator after any
461 * update to the HashTable.
462 */
463 template <typename KEY_TYPE, typename MAPPED_TYPE, HashTable_Support::IValidTraits<KEY_TYPE, MAPPED_TYPE> TRAITS>
464 class HashTable<KEY_TYPE, MAPPED_TYPE, TRAITS>::ForwardIterator {
465 public:
466 // stuff STL requires you to set to look like an iterator
467 using iterator_category = forward_iterator_tag;
468 using value_type = HashTable::value_type;
469 using difference_type = ptrdiff_t;
470 using pointer = const value_type*;
471 using reference = const value_type&;
472
473 public:
474 /**
475 * /0 overload: sets iterator to 'end' - sentinel
476 * /1 (data) overload: sets iterator to begin
477 * /2 (data,startAt) overload: sets iterator to startAt
478 */
479 constexpr ForwardIterator () noexcept = default;
480 explicit constexpr ForwardIterator (const HashTable* data) noexcept;
481 explicit constexpr ForwardIterator (const HashTable* data, UnderlyingIteratorRep startAt) noexcept;
482 constexpr ForwardIterator (const ForwardIterator&) noexcept = default;
483 constexpr ForwardIterator (ForwardIterator&&) noexcept = default;
484
485 public:
486 nonvirtual ForwardIterator& operator= (const ForwardIterator&) = default;
487 nonvirtual ForwardIterator& operator= (ForwardIterator&&) noexcept = default;
488
489#if qStroika_Foundation_Debug_AssertionsChecked
490 public:
492#endif
493
494 public:
495 /**
496 * return true if iterator not Done
497 */
498 explicit operator bool () const;
499
500 public:
501 nonvirtual bool Done () const noexcept;
502
503 public:
504 nonvirtual const value_type& operator* () const; // Error to call if Done (), otherwise OK
505
506 public:
507 nonvirtual const value_type* operator->() const; // Error to call if Done (), otherwise OK
508
509#if 0
510 public:
511 /**
512 * \note Runtime performance/complexity:
513 * Average/WorseCase: O(N) - super slow cuz have to traverse on average half the list
514 *
515 * \pre data == fData_ argument constructed with (or as adjusted by Move...); api takes extra param so release builds need not store fData_
516 */
517 nonvirtual size_t CurrentIndex (const HashTable* data) const;
518#endif
519
520 public:
521 /**
522 * \pre GetUnderlyingData() == rhs.GetUnderlyingData (), or special case of one or the other is nullptr
523 */
524 constexpr bool operator== (const ForwardIterator& rhs) const;
525
526 public:
527 constexpr UnderlyingIteratorRep GetUnderlyingIteratorRep () const;
528
529 public:
530 nonvirtual void SetUnderlyingIteratorRep (const UnderlyingIteratorRep l);
531
532 public:
533 nonvirtual ForwardIterator& operator++ ();
534 nonvirtual ForwardIterator operator++ (int);
535
536 public:
537 // safe to update in place (doesn't change iterators) since doesn't change order of list (since not updating key)
538 template <typename CHECKED_T = MAPPED_TYPE>
539 nonvirtual void UpdateValue (ArgByValueType<CHECKED_T> newValue)
540 requires (not same_as<MAPPED_TYPE, void>);
541
542 public:
543 /**
544 * For debugging, assert the iterator data matches argument data
545 */
546 constexpr void AssertDataMatches (const HashTable* data) const;
547
548 public:
549 constexpr void Invariant () const noexcept;
550
551#if qStroika_Foundation_Debug_AssertionsChecked
552 private:
553 nonvirtual void Invariant_ () const noexcept;
554#endif
555
556 private:
557 // to make == compares simpler
558 nonvirtual void AdvanceOverEmptyBuckets_ ();
559
560 private:
561 const HashTable* fData_{nullptr}; // sentinel value indicating DONE
562 size_t fBucketIndex_{0};
563 size_t fIntraBucketIndex_{0};
564
565 private:
566 friend class HashTable;
567 };
568 static_assert (ranges::input_range<HashTable<int>>); // smoke test - make sure basic iteration etc should work (allows formattable to work)
569
570}
571
572/*
573 ********************************************************************************
574 ***************************** Implementation Details ***************************
575 ********************************************************************************
576 */
577#include "HashTable.inl"
578
579#endif /*_Stroika_Foundation_Containers_DataStructures_HashTable_h_ */
implement hash table support in a lightweight standard template library style. Use traits to describe...
Definition HashTable.h:132
nonvirtual ForwardIterator erase(const ForwardIterator &i)
stdlib like names and semantics (though may want to rethink the ForwardIterator vs UnderlyingIterator...
nonvirtual void Apply(FUNCTION &&doToElement, Execution::SequencePolicy seq=Execution::SequencePolicy::eDEFAULT) const
nonvirtual KeyEqualsComparerType key_eq() const
Definition HashTable.inl:36
nonvirtual void Update(const ForwardIterator &it, ArgByValueType< CHECKED_T > newValue)
nonvirtual bool contains(ArgByValueType< key_type > key) const
nonvirtual void insert(const pair< KEY_TYPE, MAPPED_TYPE > &p)
somewhat stdlib-like names - that will do what is expected of someone from stdc++,...
nonvirtual void ReHash(size_t newBucketCount)
nonvirtual ForwardIterator Find(ArgByValueType< key_type > key) const
nonvirtual ForwardIterator find(ArgByValueType< key_type > key) const
stdlib-ish API for 'Find' - returns iterator for found object in hashtable
nonvirtual size_t bucket_size(size_t bucketIdx) const
nonvirtual void Remove(const ForwardIterator &i, ForwardIterator *nextI=nullptr)
nonvirtual float load_factor() const
average number of elements per bucket
nonvirtual bool Add(const value_type &t)
Add an item (key value pair typically, but the value can be void). Return true on list change; Respec...
Definition HashTable.inl:70
nonvirtual float max_load_factor() const
average number of elements per bucket
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual size_t size() const noexcept
validated the HashTable provided TRAITS object looks healthy (for better compiler diagnostics and usa...
Definition HashTable.h:108
check argument FUNCTION is callable with a HASHABLE_T, and produces (something convertible to) size_t
Definition HashBase.h:28
conditional_t<(sizeof(CHECK_T)<=2 *sizeof(void *)) and is_trivially_copyable_v< CHECK_T >, CHECK_T, const CHECK_T & > ArgByValueType
This is an alias for 'T' - but how we want to pass it on stack as formal parameter.
Definition TypeHints.h:32
constexpr void AssertDataMatches(const DoublyLinkedList *data) const
nonvirtual ForwardIterator & operator++() noexcept
nonvirtual size_t CurrentIndex(const DoublyLinkedList *data) const
AddOrExtendOrReplaceMode
Mode flag to say if Adding to a container replaces, or if the first addition wins (Logically AddOrExt...
SequencePolicy
equivalent which of 4 types being used std::execution::sequenced_policy, parallel_policy,...
used internally to select HashTable implementation strategies. NOT YET IMPLEMENTED OR THOUGHT OUT (se...
Definition HashTable.h:42
use as LAYOUT_OPTIONS for HashTable DefaultTraits<> template
Definition HashTable.h:48
used internally to select HashTable implementation strategies. Callers dont use directly,...
Definition HashTable.h:37