aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/restricted/abseil-cpp-tstring/y_absl/container/internal/raw_hash_set.h
blob: f3e9d12445043398425d041f7f3028a454a00f76 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
// Copyright 2018 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// An open-addressing
// hashtable with quadratic probing.
//
// This is a low level hashtable on top of which different interfaces can be
// implemented, like flat_hash_set, node_hash_set, string_hash_set, etc.
//
// The table interface is similar to that of std::unordered_set. Notable
// differences are that most member functions support heterogeneous keys when
// BOTH the hash and eq functions are marked as transparent. They do so by
// providing a typedef called `is_transparent`.
//
// When heterogeneous lookup is enabled, functions that take key_type act as if
// they have an overload set like:
//
//   iterator find(const key_type& key);
//   template <class K>
//   iterator find(const K& key);
//
//   size_type erase(const key_type& key);
//   template <class K>
//   size_type erase(const K& key);
//
//   std::pair<iterator, iterator> equal_range(const key_type& key);
//   template <class K>
//   std::pair<iterator, iterator> equal_range(const K& key);
//
// When heterogeneous lookup is disabled, only the explicit `key_type` overloads
// exist.
//
// find() also supports passing the hash explicitly:
//
//   iterator find(const key_type& key, size_t hash);
//   template <class U>
//   iterator find(const U& key, size_t hash);
//
// In addition the pointer to element and iterator stability guarantees are
// weaker: all iterators and pointers are invalidated after a new element is
// inserted.
//
// IMPLEMENTATION DETAILS
//
// # Table Layout
//
// A raw_hash_set's backing array consists of control bytes followed by slots
// that may or may not contain objects.
//
// The layout of the backing array, for `capacity` slots, is thus, as a
// pseudo-struct:
//
//   struct BackingArray {
//     // Sampling handler. This field isn't present when the sampling is
//     // disabled or this allocation hasn't been selected for sampling.
//     HashtablezInfoHandle infoz_;
//     // The number of elements we can insert before growing the capacity.
//     size_t growth_left;
//     // Control bytes for the "real" slots.
//     ctrl_t ctrl[capacity];
//     // Always `ctrl_t::kSentinel`. This is used by iterators to find when to
//     // stop and serves no other purpose.
//     ctrl_t sentinel;
//     // A copy of the first `kWidth - 1` elements of `ctrl`. This is used so
//     // that if a probe sequence picks a value near the end of `ctrl`,
//     // `Group` will have valid control bytes to look at.
//     ctrl_t clones[kWidth - 1];
//     // The actual slot data.
//     slot_type slots[capacity];
//   };
//
// The length of this array is computed by `RawHashSetLayout::alloc_size` below.
//
// Control bytes (`ctrl_t`) are bytes (collected into groups of a
// platform-specific size) that define the state of the corresponding slot in
// the slot array. Group manipulation is tightly optimized to be as efficient
// as possible: SSE and friends on x86, clever bit operations on other arches.
//
//      Group 1         Group 2        Group 3
// +---------------+---------------+---------------+
// | | | | | | | | | | | | | | | | | | | | | | | | |
// +---------------+---------------+---------------+
//
// Each control byte is either a special value for empty slots, deleted slots
// (sometimes called *tombstones*), and a special end-of-table marker used by
// iterators, or, if occupied, seven bits (H2) from the hash of the value in the
// corresponding slot.
//
// Storing control bytes in a separate array also has beneficial cache effects,
// since more logical slots will fit into a cache line.
//
// # Small Object Optimization (SOO)
//
// When the size/alignment of the value_type and the capacity of the table are
// small, we enable small object optimization and store the values inline in
// the raw_hash_set object. This optimization allows us to avoid
// allocation/deallocation as well as cache/dTLB misses.
//
// # Hashing
//
// We compute two separate hashes, `H1` and `H2`, from the hash of an object.
// `H1(hash(x))` is an index into `slots`, and essentially the starting point
// for the probe sequence. `H2(hash(x))` is a 7-bit value used to filter out
// objects that cannot possibly be the one we are looking for.
//
// # Table operations.
//
// The key operations are `insert`, `find`, and `erase`.
//
// Since `insert` and `erase` are implemented in terms of `find`, we describe
// `find` first. To `find` a value `x`, we compute `hash(x)`. From
// `H1(hash(x))` and the capacity, we construct a `probe_seq` that visits every
// group of slots in some interesting order.
//
// We now walk through these indices. At each index, we select the entire group
// starting with that index and extract potential candidates: occupied slots
// with a control byte equal to `H2(hash(x))`. If we find an empty slot in the
// group, we stop and return an error. Each candidate slot `y` is compared with
// `x`; if `x == y`, we are done and return `&y`; otherwise we continue to the
// next probe index. Tombstones effectively behave like full slots that never
// match the value we're looking for.
//
// The `H2` bits ensure when we compare a slot to an object with `==`, we are
// likely to have actually found the object.  That is, the chance is low that
// `==` is called and returns `false`.  Thus, when we search for an object, we
// are unlikely to call `==` many times.  This likelyhood can be analyzed as
// follows (assuming that H2 is a random enough hash function).
//
// Let's assume that there are `k` "wrong" objects that must be examined in a
// probe sequence.  For example, when doing a `find` on an object that is in the
// table, `k` is the number of objects between the start of the probe sequence
// and the final found object (not including the final found object).  The
// expected number of objects with an H2 match is then `k/128`.  Measurements
// and analysis indicate that even at high load factors, `k` is less than 32,
// meaning that the number of "false positive" comparisons we must perform is
// less than 1/8 per `find`.

// `insert` is implemented in terms of `unchecked_insert`, which inserts a
// value presumed to not be in the table (violating this requirement will cause
// the table to behave erratically). Given `x` and its hash `hash(x)`, to insert
// it, we construct a `probe_seq` once again, and use it to find the first
// group with an unoccupied (empty *or* deleted) slot. We place `x` into the
// first such slot in the group and mark it as full with `x`'s H2.
//
// To `insert`, we compose `unchecked_insert` with `find`. We compute `h(x)` and
// perform a `find` to see if it's already present; if it is, we're done. If
// it's not, we may decide the table is getting overcrowded (i.e. the load
// factor is greater than 7/8 for big tables; `is_small()` tables use a max load
// factor of 1); in this case, we allocate a bigger array, `unchecked_insert`
// each element of the table into the new array (we know that no insertion here
// will insert an already-present value), and discard the old backing array. At
// this point, we may `unchecked_insert` the value `x`.
//
// Below, `unchecked_insert` is partly implemented by `prepare_insert`, which
// presents a viable, initialized slot pointee to the caller.
//
// `erase` is implemented in terms of `erase_at`, which takes an index to a
// slot. Given an offset, we simply create a tombstone and destroy its contents.
// If we can prove that the slot would not appear in a probe sequence, we can
// make the slot as empty, instead. We can prove this by observing that if a
// group has any empty slots, it has never been full (assuming we never create
// an empty slot in a group with no empties, which this heuristic guarantees we
// never do) and find would stop at this group anyways (since it does not probe
// beyond groups with empties).
//
// `erase` is `erase_at` composed with `find`: if we
// have a value `x`, we can perform a `find`, and then `erase_at` the resulting
// slot.
//
// To iterate, we simply traverse the array, skipping empty and deleted slots
// and stopping when we hit a `kSentinel`.

#ifndef Y_ABSL_CONTAINER_INTERNAL_RAW_HASH_SET_H_
#define Y_ABSL_CONTAINER_INTERNAL_RAW_HASH_SET_H_

#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <initializer_list>
#include <iterator>
#include <limits>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>

#include "y_absl/base/attributes.h"
#include "y_absl/base/config.h"
#include "y_absl/base/internal/endian.h"
#include "y_absl/base/internal/raw_logging.h"
#include "y_absl/base/macros.h"
#include "y_absl/base/optimization.h"
#include "y_absl/base/options.h"
#include "y_absl/base/port.h"
#include "y_absl/base/prefetch.h"
#include "y_absl/container/internal/common.h"  // IWYU pragma: export // for node_handle
#include "y_absl/container/internal/compressed_tuple.h"
#include "y_absl/container/internal/container_memory.h"
#include "y_absl/container/internal/hash_policy_traits.h"
#include "y_absl/container/internal/hashtable_debug_hooks.h"
#include "y_absl/container/internal/hashtablez_sampler.h"
#include "y_absl/memory/memory.h"
#include "y_absl/meta/type_traits.h"
#include "y_absl/numeric/bits.h"
#include "y_absl/utility/utility.h"

#ifdef Y_ABSL_INTERNAL_HAVE_SSE2
#include <emmintrin.h>
#endif

#ifdef Y_ABSL_INTERNAL_HAVE_SSSE3
#include <tmmintrin.h>
#endif

#ifdef _MSC_VER
#include <intrin.h>
#endif

#ifdef Y_ABSL_INTERNAL_HAVE_ARM_NEON
#include <arm_neon.h>
#endif

namespace y_absl {
Y_ABSL_NAMESPACE_BEGIN
namespace container_internal {

#ifdef Y_ABSL_SWISSTABLE_ENABLE_GENERATIONS
#error Y_ABSL_SWISSTABLE_ENABLE_GENERATIONS cannot be directly set
#elif (defined(Y_ABSL_HAVE_ADDRESS_SANITIZER) ||   \
       defined(Y_ABSL_HAVE_HWADDRESS_SANITIZER) || \
       defined(Y_ABSL_HAVE_MEMORY_SANITIZER)) &&   \
    !defined(NDEBUG_SANITIZER)  // If defined, performance is important.
// When compiled in sanitizer mode, we add generation integers to the backing
// array and iterators. In the backing array, we store the generation between
// the control bytes and the slots. When iterators are dereferenced, we assert
// that the container has not been mutated in a way that could cause iterator
// invalidation since the iterator was initialized.
#define Y_ABSL_SWISSTABLE_ENABLE_GENERATIONS
#endif

// We use uint8_t so we don't need to worry about padding.
using GenerationType = uint8_t;

// A sentinel value for empty generations. Using 0 makes it easy to constexpr
// initialize an array of this value.
constexpr GenerationType SentinelEmptyGeneration() { return 0; }

constexpr GenerationType NextGeneration(GenerationType generation) {
  return ++generation == SentinelEmptyGeneration() ? ++generation : generation;
}

#ifdef Y_ABSL_SWISSTABLE_ENABLE_GENERATIONS
constexpr bool SwisstableGenerationsEnabled() { return true; }
constexpr size_t NumGenerationBytes() { return sizeof(GenerationType); }
#else
constexpr bool SwisstableGenerationsEnabled() { return false; }
constexpr size_t NumGenerationBytes() { return 0; }
#endif

template <typename AllocType>
void SwapAlloc(AllocType& lhs, AllocType& rhs,
               std::true_type /* propagate_on_container_swap */) {
  using std::swap;
  swap(lhs, rhs);
}
template <typename AllocType>
void SwapAlloc(AllocType& lhs, AllocType& rhs,
               std::false_type /* propagate_on_container_swap */) {
  (void)lhs;
  (void)rhs;
  assert(lhs == rhs &&
         "It's UB to call swap with unequal non-propagating allocators.");
}

template <typename AllocType>
void CopyAlloc(AllocType& lhs, AllocType& rhs,
               std::true_type /* propagate_alloc */) {
  lhs = rhs;
}
template <typename AllocType>
void CopyAlloc(AllocType&, AllocType&, std::false_type /* propagate_alloc */) {}

// The state for a probe sequence.
//
// Currently, the sequence is a triangular progression of the form
//
//   p(i) := Width * (i^2 + i)/2 + hash (mod mask + 1)
//
// The use of `Width` ensures that each probe step does not overlap groups;
// the sequence effectively outputs the addresses of *groups* (although not
// necessarily aligned to any boundary). The `Group` machinery allows us
// to check an entire group with minimal branching.
//
// Wrapping around at `mask + 1` is important, but not for the obvious reason.
// As described above, the first few entries of the control byte array
// are mirrored at the end of the array, which `Group` will find and use
// for selecting candidates. However, when those candidates' slots are
// actually inspected, there are no corresponding slots for the cloned bytes,
// so we need to make sure we've treated those offsets as "wrapping around".
//
// It turns out that this probe sequence visits every group exactly once if the
// number of groups is a power of two, since (i^2+i)/2 is a bijection in
// Z/(2^m). See https://en.wikipedia.org/wiki/Quadratic_probing
template <size_t Width>
class probe_seq {
 public:
  // Creates a new probe sequence using `hash` as the initial value of the
  // sequence and `mask` (usually the capacity of the table) as the mask to
  // apply to each value in the progression.
  probe_seq(size_t hash, size_t mask) {
    assert(((mask + 1) & mask) == 0 && "not a mask");
    mask_ = mask;
    offset_ = hash & mask_;
  }

  // The offset within the table, i.e., the value `p(i)` above.
  size_t offset() const { return offset_; }
  size_t offset(size_t i) const { return (offset_ + i) & mask_; }

  void next() {
    index_ += Width;
    offset_ += index_;
    offset_ &= mask_;
  }
  // 0-based probe index, a multiple of `Width`.
  size_t index() const { return index_; }

 private:
  size_t mask_;
  size_t offset_;
  size_t index_ = 0;
};

template <class ContainerKey, class Hash, class Eq>
struct RequireUsableKey {
  template <class PassedKey, class... Args>
  std::pair<
      decltype(std::declval<const Hash&>()(std::declval<const PassedKey&>())),
      decltype(std::declval<const Eq&>()(std::declval<const ContainerKey&>(),
                                         std::declval<const PassedKey&>()))>*
  operator()(const PassedKey&, const Args&...) const;
};

template <class E, class Policy, class Hash, class Eq, class... Ts>
struct IsDecomposable : std::false_type {};

template <class Policy, class Hash, class Eq, class... Ts>
struct IsDecomposable<
    y_absl::void_t<decltype(Policy::apply(
        RequireUsableKey<typename Policy::key_type, Hash, Eq>(),
        std::declval<Ts>()...))>,
    Policy, Hash, Eq, Ts...> : std::true_type {};

// TODO(alkis): Switch to std::is_nothrow_swappable when gcc/clang supports it.
template <class T>
constexpr bool IsNoThrowSwappable(std::true_type = {} /* is_swappable */) {
  using std::swap;
  return noexcept(swap(std::declval<T&>(), std::declval<T&>()));
}
template <class T>
constexpr bool IsNoThrowSwappable(std::false_type /* is_swappable */) {
  return false;
}

template <typename T>
uint32_t TrailingZeros(T x) {
  Y_ABSL_ASSUME(x != 0);
  return static_cast<uint32_t>(countr_zero(x));
}

// 8 bytes bitmask with most significant bit set for every byte.
constexpr uint64_t kMsbs8Bytes = 0x8080808080808080ULL;

// An abstract bitmask, such as that emitted by a SIMD instruction.
//
// Specifically, this type implements a simple bitset whose representation is
// controlled by `SignificantBits` and `Shift`. `SignificantBits` is the number
// of abstract bits in the bitset, while `Shift` is the log-base-two of the
// width of an abstract bit in the representation.
// This mask provides operations for any number of real bits set in an abstract
// bit. To add iteration on top of that, implementation must guarantee no more
// than the most significant real bit is set in a set abstract bit.
template <class T, int SignificantBits, int Shift = 0>
class NonIterableBitMask {
 public:
  explicit NonIterableBitMask(T mask) : mask_(mask) {}

  explicit operator bool() const { return this->mask_ != 0; }

  // Returns the index of the lowest *abstract* bit set in `self`.
  uint32_t LowestBitSet() const {
    return container_internal::TrailingZeros(mask_) >> Shift;
  }

  // Returns the index of the highest *abstract* bit set in `self`.
  uint32_t HighestBitSet() const {
    return static_cast<uint32_t>((bit_width(mask_) - 1) >> Shift);
  }

  // Returns the number of trailing zero *abstract* bits.
  uint32_t TrailingZeros() const {
    return container_internal::TrailingZeros(mask_) >> Shift;
  }

  // Returns the number of leading zero *abstract* bits.
  uint32_t LeadingZeros() const {
    constexpr int total_significant_bits = SignificantBits << Shift;
    constexpr int extra_bits = sizeof(T) * 8 - total_significant_bits;
    return static_cast<uint32_t>(
               countl_zero(static_cast<T>(mask_ << extra_bits))) >>
           Shift;
  }

  T mask_;
};

// Mask that can be iterable
//
// For example, when `SignificantBits` is 16 and `Shift` is zero, this is just
// an ordinary 16-bit bitset occupying the low 16 bits of `mask`. When
// `SignificantBits` is 8 and `Shift` is 3, abstract bits are represented as
// the bytes `0x00` and `0x80`, and it occupies all 64 bits of the bitmask.
// If NullifyBitsOnIteration is true (only allowed for Shift == 3),
// non zero abstract bit is allowed to have additional bits
// (e.g., `0xff`, `0x83` and `0x9c` are ok, but `0x6f` is not).
//
// For example:
//   for (int i : BitMask<uint32_t, 16>(0b101)) -> yields 0, 2
//   for (int i : BitMask<uint64_t, 8, 3>(0x0000000080800000)) -> yields 2, 3
template <class T, int SignificantBits, int Shift = 0,
          bool NullifyBitsOnIteration = false>
class BitMask : public NonIterableBitMask<T, SignificantBits, Shift> {
  using Base = NonIterableBitMask<T, SignificantBits, Shift>;
  static_assert(std::is_unsigned<T>::value, "");
  static_assert(Shift == 0 || Shift == 3, "");
  static_assert(!NullifyBitsOnIteration || Shift == 3, "");

 public:
  explicit BitMask(T mask) : Base(mask) {
    if (Shift == 3 && !NullifyBitsOnIteration) {
      assert(this->mask_ == (this->mask_ & kMsbs8Bytes));
    }
  }
  // BitMask is an iterator over the indices of its abstract bits.
  using value_type = int;
  using iterator = BitMask;
  using const_iterator = BitMask;

  BitMask& operator++() {
    if (Shift == 3 && NullifyBitsOnIteration) {
      this->mask_ &= kMsbs8Bytes;
    }
    this->mask_ &= (this->mask_ - 1);
    return *this;
  }

  uint32_t operator*() const { return Base::LowestBitSet(); }

  BitMask begin() const { return *this; }
  BitMask end() const { return BitMask(0); }

 private:
  friend bool operator==(const BitMask& a, const BitMask& b) {
    return a.mask_ == b.mask_;
  }
  friend bool operator!=(const BitMask& a, const BitMask& b) {
    return a.mask_ != b.mask_;
  }
};

using h2_t = uint8_t;

// The values here are selected for maximum performance. See the static asserts
// below for details.

// A `ctrl_t` is a single control byte, which can have one of four
// states: empty, deleted, full (which has an associated seven-bit h2_t value)
// and the sentinel. They have the following bit patterns:
//
//      empty: 1 0 0 0 0 0 0 0
//    deleted: 1 1 1 1 1 1 1 0
//       full: 0 h h h h h h h  // h represents the hash bits.
//   sentinel: 1 1 1 1 1 1 1 1
//
// These values are specifically tuned for SSE-flavored SIMD.
// The static_asserts below detail the source of these choices.
//
// We use an enum class so that when strict aliasing is enabled, the compiler
// knows ctrl_t doesn't alias other types.
enum class ctrl_t : int8_t {
  kEmpty = -128,   // 0b10000000
  kDeleted = -2,   // 0b11111110
  kSentinel = -1,  // 0b11111111
};
static_assert(
    (static_cast<int8_t>(ctrl_t::kEmpty) &
     static_cast<int8_t>(ctrl_t::kDeleted) &
     static_cast<int8_t>(ctrl_t::kSentinel) & 0x80) != 0,
    "Special markers need to have the MSB to make checking for them efficient");
static_assert(
    ctrl_t::kEmpty < ctrl_t::kSentinel && ctrl_t::kDeleted < ctrl_t::kSentinel,
    "ctrl_t::kEmpty and ctrl_t::kDeleted must be smaller than "
    "ctrl_t::kSentinel to make the SIMD test of IsEmptyOrDeleted() efficient");
static_assert(
    ctrl_t::kSentinel == static_cast<ctrl_t>(-1),
    "ctrl_t::kSentinel must be -1 to elide loading it from memory into SIMD "
    "registers (pcmpeqd xmm, xmm)");
static_assert(ctrl_t::kEmpty == static_cast<ctrl_t>(-128),
              "ctrl_t::kEmpty must be -128 to make the SIMD check for its "
              "existence efficient (psignb xmm, xmm)");
static_assert(
    (~static_cast<int8_t>(ctrl_t::kEmpty) &
     ~static_cast<int8_t>(ctrl_t::kDeleted) &
     static_cast<int8_t>(ctrl_t::kSentinel) & 0x7F) != 0,
    "ctrl_t::kEmpty and ctrl_t::kDeleted must share an unset bit that is not "
    "shared by ctrl_t::kSentinel to make the scalar test for "
    "MaskEmptyOrDeleted() efficient");
static_assert(ctrl_t::kDeleted == static_cast<ctrl_t>(-2),
              "ctrl_t::kDeleted must be -2 to make the implementation of "
              "ConvertSpecialToEmptyAndFullToDeleted efficient");

// See definition comment for why this is size 32.
Y_ABSL_DLL extern const ctrl_t kEmptyGroup[32];

// Returns a pointer to a control byte group that can be used by empty tables.
inline ctrl_t* EmptyGroup() {
  // Const must be cast away here; no uses of this function will actually write
  // to it because it is only used for empty tables.
  return const_cast<ctrl_t*>(kEmptyGroup + 16);
}

// For use in SOO iterators.
// TODO(b/289225379): we could potentially get rid of this by adding an is_soo
// bit in iterators. This would add branches but reduce cache misses.
Y_ABSL_DLL extern const ctrl_t kSooControl[17];

// Returns a pointer to a full byte followed by a sentinel byte.
inline ctrl_t* SooControl() {
  // Const must be cast away here; no uses of this function will actually write
  // to it because it is only used for SOO iterators.
  return const_cast<ctrl_t*>(kSooControl);
}
// Whether ctrl is from the SooControl array.
inline bool IsSooControl(const ctrl_t* ctrl) { return ctrl == SooControl(); }

// Returns a pointer to a generation to use for an empty hashtable.
GenerationType* EmptyGeneration();

// Returns whether `generation` is a generation for an empty hashtable that
// could be returned by EmptyGeneration().
inline bool IsEmptyGeneration(const GenerationType* generation) {
  return *generation == SentinelEmptyGeneration();
}

// Mixes a randomly generated per-process seed with `hash` and `ctrl` to
// randomize insertion order within groups.
bool ShouldInsertBackwardsForDebug(size_t capacity, size_t hash,
                                   const ctrl_t* ctrl);

Y_ABSL_ATTRIBUTE_ALWAYS_INLINE inline bool ShouldInsertBackwards(
    Y_ABSL_ATTRIBUTE_UNUSED size_t capacity, Y_ABSL_ATTRIBUTE_UNUSED size_t hash,
    Y_ABSL_ATTRIBUTE_UNUSED const ctrl_t* ctrl) {
#if defined(NDEBUG)
  return false;
#else
  return ShouldInsertBackwardsForDebug(capacity, hash, ctrl);
#endif
}

// Returns insert position for the given mask.
// We want to add entropy even when ASLR is not enabled.
// In debug build we will randomly insert in either the front or back of
// the group.
// TODO(kfm,sbenza): revisit after we do unconditional mixing
template <class Mask>
Y_ABSL_ATTRIBUTE_ALWAYS_INLINE inline auto GetInsertionOffset(
    Mask mask, Y_ABSL_ATTRIBUTE_UNUSED size_t capacity,
    Y_ABSL_ATTRIBUTE_UNUSED size_t hash,
    Y_ABSL_ATTRIBUTE_UNUSED const ctrl_t* ctrl) {
#if defined(NDEBUG)
  return mask.LowestBitSet();
#else
  return ShouldInsertBackwardsForDebug(capacity, hash, ctrl)
             ? mask.HighestBitSet()
             : mask.LowestBitSet();
#endif
}

// Returns a per-table, hash salt, which changes on resize. This gets mixed into
// H1 to randomize iteration order per-table.
//
// The seed consists of the ctrl_ pointer, which adds enough entropy to ensure
// non-determinism of iteration order in most cases.
inline size_t PerTableSalt(const ctrl_t* ctrl) {
  // The low bits of the pointer have little or no entropy because of
  // alignment. We shift the pointer to try to use higher entropy bits. A
  // good number seems to be 12 bits, because that aligns with page size.
  return reinterpret_cast<uintptr_t>(ctrl) >> 12;
}
// Extracts the H1 portion of a hash: 57 bits mixed with a per-table salt.
inline size_t H1(size_t hash, const ctrl_t* ctrl) {
  return (hash >> 7) ^ PerTableSalt(ctrl);
}

// Extracts the H2 portion of a hash: the 7 bits not used for H1.
//
// These are used as an occupied control byte.
inline h2_t H2(size_t hash) { return hash & 0x7F; }

// Helpers for checking the state of a control byte.
inline bool IsEmpty(ctrl_t c) { return c == ctrl_t::kEmpty; }
inline bool IsFull(ctrl_t c) {
  // Cast `c` to the underlying type instead of casting `0` to `ctrl_t` as `0`
  // is not a value in the enum. Both ways are equivalent, but this way makes
  // linters happier.
  return static_cast<std::underlying_type_t<ctrl_t>>(c) >= 0;
}
inline bool IsDeleted(ctrl_t c) { return c == ctrl_t::kDeleted; }
inline bool IsEmptyOrDeleted(ctrl_t c) { return c < ctrl_t::kSentinel; }

#ifdef Y_ABSL_INTERNAL_HAVE_SSE2
// Quick reference guide for intrinsics used below:
//
// * __m128i: An XMM (128-bit) word.
//
// * _mm_setzero_si128: Returns a zero vector.
// * _mm_set1_epi8:     Returns a vector with the same i8 in each lane.
//
// * _mm_subs_epi8:    Saturating-subtracts two i8 vectors.
// * _mm_and_si128:    Ands two i128s together.
// * _mm_or_si128:     Ors two i128s together.
// * _mm_andnot_si128: And-nots two i128s together.
//
// * _mm_cmpeq_epi8: Component-wise compares two i8 vectors for equality,
//                   filling each lane with 0x00 or 0xff.
// * _mm_cmpgt_epi8: Same as above, but using > rather than ==.
//
// * _mm_loadu_si128:  Performs an unaligned load of an i128.
// * _mm_storeu_si128: Performs an unaligned store of an i128.
//
// * _mm_sign_epi8:     Retains, negates, or zeroes each i8 lane of the first
//                      argument if the corresponding lane of the second
//                      argument is positive, negative, or zero, respectively.
// * _mm_movemask_epi8: Selects the sign bit out of each i8 lane and produces a
//                      bitmask consisting of those bits.
// * _mm_shuffle_epi8:  Selects i8s from the first argument, using the low
//                      four bits of each i8 lane in the second argument as
//                      indices.

// https://github.com/abseil/abseil-cpp/issues/209
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87853
// _mm_cmpgt_epi8 is broken under GCC with -funsigned-char
// Work around this by using the portable implementation of Group
// when using -funsigned-char under GCC.
inline __m128i _mm_cmpgt_epi8_fixed(__m128i a, __m128i b) {
#if defined(__GNUC__) && !defined(__clang__)
  if (std::is_unsigned<char>::value) {
    const __m128i mask = _mm_set1_epi8(0x80);
    const __m128i diff = _mm_subs_epi8(b, a);
    return _mm_cmpeq_epi8(_mm_and_si128(diff, mask), mask);
  }
#endif
  return _mm_cmpgt_epi8(a, b);
}

struct GroupSse2Impl {
  static constexpr size_t kWidth = 16;  // the number of slots per group

  explicit GroupSse2Impl(const ctrl_t* pos) {
    ctrl = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pos));
  }

  // Returns a bitmask representing the positions of slots that match hash.
  BitMask<uint16_t, kWidth> Match(h2_t hash) const {
    auto match = _mm_set1_epi8(static_cast<char>(hash));
    BitMask<uint16_t, kWidth> result = BitMask<uint16_t, kWidth>(0);
    result = BitMask<uint16_t, kWidth>(
        static_cast<uint16_t>(_mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl))));
    return result;
  }

  // Returns a bitmask representing the positions of empty slots.
  NonIterableBitMask<uint16_t, kWidth> MaskEmpty() const {
#ifdef Y_ABSL_INTERNAL_HAVE_SSSE3
    // This only works because ctrl_t::kEmpty is -128.
    return NonIterableBitMask<uint16_t, kWidth>(
        static_cast<uint16_t>(_mm_movemask_epi8(_mm_sign_epi8(ctrl, ctrl))));
#else
    auto match = _mm_set1_epi8(static_cast<char>(ctrl_t::kEmpty));
    return NonIterableBitMask<uint16_t, kWidth>(
        static_cast<uint16_t>(_mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl))));
#endif
  }

  // Returns a bitmask representing the positions of full slots.
  // Note: for `is_small()` tables group may contain the "same" slot twice:
  // original and mirrored.
  BitMask<uint16_t, kWidth> MaskFull() const {
    return BitMask<uint16_t, kWidth>(
        static_cast<uint16_t>(_mm_movemask_epi8(ctrl) ^ 0xffff));
  }

  // Returns a bitmask representing the positions of non full slots.
  // Note: this includes: kEmpty, kDeleted, kSentinel.
  // It is useful in contexts when kSentinel is not present.
  auto MaskNonFull() const {
    return BitMask<uint16_t, kWidth>(
        static_cast<uint16_t>(_mm_movemask_epi8(ctrl)));
  }

  // Returns a bitmask representing the positions of empty or deleted slots.
  NonIterableBitMask<uint16_t, kWidth> MaskEmptyOrDeleted() const {
    auto special = _mm_set1_epi8(static_cast<char>(ctrl_t::kSentinel));
    return NonIterableBitMask<uint16_t, kWidth>(static_cast<uint16_t>(
        _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl))));
  }

  // Returns the number of trailing empty or deleted elements in the group.
  uint32_t CountLeadingEmptyOrDeleted() const {
    auto special = _mm_set1_epi8(static_cast<char>(ctrl_t::kSentinel));
    return TrailingZeros(static_cast<uint32_t>(
        _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl)) + 1));
  }

  void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const {
    auto msbs = _mm_set1_epi8(static_cast<char>(-128));
    auto x126 = _mm_set1_epi8(126);
#ifdef Y_ABSL_INTERNAL_HAVE_SSSE3
    auto res = _mm_or_si128(_mm_shuffle_epi8(x126, ctrl), msbs);
#else
    auto zero = _mm_setzero_si128();
    auto special_mask = _mm_cmpgt_epi8_fixed(zero, ctrl);
    auto res = _mm_or_si128(msbs, _mm_andnot_si128(special_mask, x126));
#endif
    _mm_storeu_si128(reinterpret_cast<__m128i*>(dst), res);
  }

  __m128i ctrl;
};
#endif  // Y_ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2

#if defined(Y_ABSL_INTERNAL_HAVE_ARM_NEON) && defined(Y_ABSL_IS_LITTLE_ENDIAN)
struct GroupAArch64Impl {
  static constexpr size_t kWidth = 8;

  explicit GroupAArch64Impl(const ctrl_t* pos) {
    ctrl = vld1_u8(reinterpret_cast<const uint8_t*>(pos));
  }

  auto Match(h2_t hash) const {
    uint8x8_t dup = vdup_n_u8(hash);
    auto mask = vceq_u8(ctrl, dup);
    return BitMask<uint64_t, kWidth, /*Shift=*/3,
                   /*NullifyBitsOnIteration=*/true>(
        vget_lane_u64(vreinterpret_u64_u8(mask), 0));
  }

  NonIterableBitMask<uint64_t, kWidth, 3> MaskEmpty() const {
    uint64_t mask =
        vget_lane_u64(vreinterpret_u64_u8(vceq_s8(
                          vdup_n_s8(static_cast<int8_t>(ctrl_t::kEmpty)),
                          vreinterpret_s8_u8(ctrl))),
                      0);
    return NonIterableBitMask<uint64_t, kWidth, 3>(mask);
  }

  // Returns a bitmask representing the positions of full slots.
  // Note: for `is_small()` tables group may contain the "same" slot twice:
  // original and mirrored.
  auto MaskFull() const {
    uint64_t mask = vget_lane_u64(
        vreinterpret_u64_u8(vcge_s8(vreinterpret_s8_u8(ctrl),
                                    vdup_n_s8(static_cast<int8_t>(0)))),
        0);
    return BitMask<uint64_t, kWidth, /*Shift=*/3,
                   /*NullifyBitsOnIteration=*/true>(mask);
  }

  // Returns a bitmask representing the positions of non full slots.
  // Note: this includes: kEmpty, kDeleted, kSentinel.
  // It is useful in contexts when kSentinel is not present.
  auto MaskNonFull() const {
    uint64_t mask = vget_lane_u64(
        vreinterpret_u64_u8(vclt_s8(vreinterpret_s8_u8(ctrl),
                                    vdup_n_s8(static_cast<int8_t>(0)))),
        0);
    return BitMask<uint64_t, kWidth, /*Shift=*/3,
                   /*NullifyBitsOnIteration=*/true>(mask);
  }

  NonIterableBitMask<uint64_t, kWidth, 3> MaskEmptyOrDeleted() const {
    uint64_t mask =
        vget_lane_u64(vreinterpret_u64_u8(vcgt_s8(
                          vdup_n_s8(static_cast<int8_t>(ctrl_t::kSentinel)),
                          vreinterpret_s8_u8(ctrl))),
                      0);
    return NonIterableBitMask<uint64_t, kWidth, 3>(mask);
  }

  uint32_t CountLeadingEmptyOrDeleted() const {
    uint64_t mask =
        vget_lane_u64(vreinterpret_u64_u8(vcle_s8(
                          vdup_n_s8(static_cast<int8_t>(ctrl_t::kSentinel)),
                          vreinterpret_s8_u8(ctrl))),
                      0);
    // Similar to MaskEmptyorDeleted() but we invert the logic to invert the
    // produced bitfield. We then count number of trailing zeros.
    // Clang and GCC optimize countr_zero to rbit+clz without any check for 0,
    // so we should be fine.
    return static_cast<uint32_t>(countr_zero(mask)) >> 3;
  }

  void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const {
    uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(ctrl), 0);
    constexpr uint64_t slsbs = 0x0202020202020202ULL;
    constexpr uint64_t midbs = 0x7e7e7e7e7e7e7e7eULL;
    auto x = slsbs & (mask >> 6);
    auto res = (x + midbs) | kMsbs8Bytes;
    little_endian::Store64(dst, res);
  }

  uint8x8_t ctrl;
};
#endif  // Y_ABSL_INTERNAL_HAVE_ARM_NEON && Y_ABSL_IS_LITTLE_ENDIAN

struct GroupPortableImpl {
  static constexpr size_t kWidth = 8;

  explicit GroupPortableImpl(const ctrl_t* pos)
      : ctrl(little_endian::Load64(pos)) {}

  BitMask<uint64_t, kWidth, 3> Match(h2_t hash) const {
    // For the technique, see:
    // http://graphics.stanford.edu/~seander/bithacks.html##ValueInWord
    // (Determine if a word has a byte equal to n).
    //
    // Caveat: there are false positives but:
    // - they only occur if there is a real match
    // - they never occur on ctrl_t::kEmpty, ctrl_t::kDeleted, ctrl_t::kSentinel
    // - they will be handled gracefully by subsequent checks in code
    //
    // Example:
    //   v = 0x1716151413121110
    //   hash = 0x12
    //   retval = (v - lsbs) & ~v & msbs = 0x0000000080800000
    constexpr uint64_t lsbs = 0x0101010101010101ULL;
    auto x = ctrl ^ (lsbs * hash);
    return BitMask<uint64_t, kWidth, 3>((x - lsbs) & ~x & kMsbs8Bytes);
  }

  NonIterableBitMask<uint64_t, kWidth, 3> MaskEmpty() const {
    return NonIterableBitMask<uint64_t, kWidth, 3>((ctrl & ~(ctrl << 6)) &
                                                   kMsbs8Bytes);
  }

  // Returns a bitmask representing the positions of full slots.
  // Note: for `is_small()` tables group may contain the "same" slot twice:
  // original and mirrored.
  BitMask<uint64_t, kWidth, 3> MaskFull() const {
    return BitMask<uint64_t, kWidth, 3>((ctrl ^ kMsbs8Bytes) & kMsbs8Bytes);
  }

  // Returns a bitmask representing the positions of non full slots.
  // Note: this includes: kEmpty, kDeleted, kSentinel.
  // It is useful in contexts when kSentinel is not present.
  auto MaskNonFull() const {
    return BitMask<uint64_t, kWidth, 3>(ctrl & kMsbs8Bytes);
  }

  NonIterableBitMask<uint64_t, kWidth, 3> MaskEmptyOrDeleted() const {
    return NonIterableBitMask<uint64_t, kWidth, 3>((ctrl & ~(ctrl << 7)) &
                                                   kMsbs8Bytes);
  }

  uint32_t CountLeadingEmptyOrDeleted() const {
    // ctrl | ~(ctrl >> 7) will have the lowest bit set to zero for kEmpty and
    // kDeleted. We lower all other bits and count number of trailing zeros.
    constexpr uint64_t bits = 0x0101010101010101ULL;
    return static_cast<uint32_t>(countr_zero((ctrl | ~(ctrl >> 7)) & bits) >>
                                 3);
  }

  void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const {
    constexpr uint64_t lsbs = 0x0101010101010101ULL;
    auto x = ctrl & kMsbs8Bytes;
    auto res = (~x + (x >> 7)) & ~lsbs;
    little_endian::Store64(dst, res);
  }

  uint64_t ctrl;
};

#ifdef Y_ABSL_INTERNAL_HAVE_SSE2
using Group = GroupSse2Impl;
using GroupFullEmptyOrDeleted = GroupSse2Impl;
#elif defined(Y_ABSL_INTERNAL_HAVE_ARM_NEON) && defined(Y_ABSL_IS_LITTLE_ENDIAN)
using Group = GroupAArch64Impl;
// For Aarch64, we use the portable implementation for counting and masking
// full, empty or deleted group elements. This is to avoid the latency of moving
// between data GPRs and Neon registers when it does not provide a benefit.
// Using Neon is profitable when we call Match(), but is not when we don't,
// which is the case when we do *EmptyOrDeleted and MaskFull operations.
// It is difficult to make a similar approach beneficial on other architectures
// such as x86 since they have much lower GPR <-> vector register transfer
// latency and 16-wide Groups.
using GroupFullEmptyOrDeleted = GroupPortableImpl;
#else
using Group = GroupPortableImpl;
using GroupFullEmptyOrDeleted = GroupPortableImpl;
#endif

// When there is an insertion with no reserved growth, we rehash with
// probability `min(1, RehashProbabilityConstant() / capacity())`. Using a
// constant divided by capacity ensures that inserting N elements is still O(N)
// in the average case. Using the constant 16 means that we expect to rehash ~8
// times more often than when generations are disabled. We are adding expected
// rehash_probability * #insertions/capacity_growth = 16/capacity * ((7/8 -
// 7/16) * capacity)/capacity_growth = ~7 extra rehashes per capacity growth.
inline size_t RehashProbabilityConstant() { return 16; }

class CommonFieldsGenerationInfoEnabled {
  // A sentinel value for reserved_growth_ indicating that we just ran out of
  // reserved growth on the last insertion. When reserve is called and then
  // insertions take place, reserved_growth_'s state machine is N, ..., 1,
  // kReservedGrowthJustRanOut, 0.
  static constexpr size_t kReservedGrowthJustRanOut =
      (std::numeric_limits<size_t>::max)();

 public:
  CommonFieldsGenerationInfoEnabled() = default;
  CommonFieldsGenerationInfoEnabled(CommonFieldsGenerationInfoEnabled&& that)
      : reserved_growth_(that.reserved_growth_),
        reservation_size_(that.reservation_size_),
        generation_(that.generation_) {
    that.reserved_growth_ = 0;
    that.reservation_size_ = 0;
    that.generation_ = EmptyGeneration();
  }
  CommonFieldsGenerationInfoEnabled& operator=(
      CommonFieldsGenerationInfoEnabled&&) = default;

  // Whether we should rehash on insert in order to detect bugs of using invalid
  // references. We rehash on the first insertion after reserved_growth_ reaches
  // 0 after a call to reserve. We also do a rehash with low probability
  // whenever reserved_growth_ is zero.
  bool should_rehash_for_bug_detection_on_insert(const ctrl_t* ctrl,
                                                 size_t capacity) const;
  // Similar to above, except that we don't depend on reserved_growth_.
  bool should_rehash_for_bug_detection_on_move(const ctrl_t* ctrl,
                                               size_t capacity) const;
  void maybe_increment_generation_on_insert() {
    if (reserved_growth_ == kReservedGrowthJustRanOut) reserved_growth_ = 0;

    if (reserved_growth_ > 0) {
      if (--reserved_growth_ == 0) reserved_growth_ = kReservedGrowthJustRanOut;
    } else {
      increment_generation();
    }
  }
  void increment_generation() { *generation_ = NextGeneration(*generation_); }
  void reset_reserved_growth(size_t reservation, size_t size) {
    reserved_growth_ = reservation - size;
  }
  size_t reserved_growth() const { return reserved_growth_; }
  void set_reserved_growth(size_t r) { reserved_growth_ = r; }
  size_t reservation_size() const { return reservation_size_; }
  void set_reservation_size(size_t r) { reservation_size_ = r; }
  GenerationType generation() const { return *generation_; }
  void set_generation(GenerationType g) { *generation_ = g; }
  GenerationType* generation_ptr() const { return generation_; }
  void set_generation_ptr(GenerationType* g) { generation_ = g; }

 private:
  // The number of insertions remaining that are guaranteed to not rehash due to
  // a prior call to reserve. Note: we store reserved growth in addition to
  // reservation size because calls to erase() decrease size_ but don't decrease
  // reserved growth.
  size_t reserved_growth_ = 0;
  // The maximum argument to reserve() since the container was cleared. We need
  // to keep track of this, in addition to reserved growth, because we reset
  // reserved growth to this when erase(begin(), end()) is called.
  size_t reservation_size_ = 0;
  // Pointer to the generation counter, which is used to validate iterators and
  // is stored in the backing array between the control bytes and the slots.
  // Note that we can't store the generation inside the container itself and
  // keep a pointer to the container in the iterators because iterators must
  // remain valid when the container is moved.
  // Note: we could derive this pointer from the control pointer, but it makes
  // the code more complicated, and there's a benefit in having the sizes of
  // raw_hash_set in sanitizer mode and non-sanitizer mode a bit more different,
  // which is that tests are less likely to rely on the size remaining the same.
  GenerationType* generation_ = EmptyGeneration();
};

class CommonFieldsGenerationInfoDisabled {
 public:
  CommonFieldsGenerationInfoDisabled() = default;
  CommonFieldsGenerationInfoDisabled(CommonFieldsGenerationInfoDisabled&&) =
      default;
  CommonFieldsGenerationInfoDisabled& operator=(
      CommonFieldsGenerationInfoDisabled&&) = default;

  bool should_rehash_for_bug_detection_on_insert(const ctrl_t*, size_t) const {
    return false;
  }
  bool should_rehash_for_bug_detection_on_move(const ctrl_t*, size_t) const {
    return false;
  }
  void maybe_increment_generation_on_insert() {}
  void increment_generation() {}
  void reset_reserved_growth(size_t, size_t) {}
  size_t reserved_growth() const { return 0; }
  void set_reserved_growth(size_t) {}
  size_t reservation_size() const { return 0; }
  void set_reservation_size(size_t) {}
  GenerationType generation() const { return 0; }
  void set_generation(GenerationType) {}
  GenerationType* generation_ptr() const { return nullptr; }
  void set_generation_ptr(GenerationType*) {}
};

class HashSetIteratorGenerationInfoEnabled {
 public:
  HashSetIteratorGenerationInfoEnabled() = default;
  explicit HashSetIteratorGenerationInfoEnabled(
      const GenerationType* generation_ptr)
      : generation_ptr_(generation_ptr), generation_(*generation_ptr) {}

  GenerationType generation() const { return generation_; }
  void reset_generation() { generation_ = *generation_ptr_; }
  const GenerationType* generation_ptr() const { return generation_ptr_; }
  void set_generation_ptr(const GenerationType* ptr) { generation_ptr_ = ptr; }

 private:
  const GenerationType* generation_ptr_ = EmptyGeneration();
  GenerationType generation_ = *generation_ptr_;
};

class HashSetIteratorGenerationInfoDisabled {
 public:
  HashSetIteratorGenerationInfoDisabled() = default;
  explicit HashSetIteratorGenerationInfoDisabled(const GenerationType*) {}

  GenerationType generation() const { return 0; }
  void reset_generation() {}
  const GenerationType* generation_ptr() const { return nullptr; }
  void set_generation_ptr(const GenerationType*) {}
};

#ifdef Y_ABSL_SWISSTABLE_ENABLE_GENERATIONS
using CommonFieldsGenerationInfo = CommonFieldsGenerationInfoEnabled;
using HashSetIteratorGenerationInfo = HashSetIteratorGenerationInfoEnabled;
#else
using CommonFieldsGenerationInfo = CommonFieldsGenerationInfoDisabled;
using HashSetIteratorGenerationInfo = HashSetIteratorGenerationInfoDisabled;
#endif

// Stored the information regarding number of slots we can still fill
// without needing to rehash.
//
// We want to ensure sufficient number of empty slots in the table in order
// to keep probe sequences relatively short. Empty slot in the probe group
// is required to stop probing.
//
// Tombstones (kDeleted slots) are not included in the growth capacity,
// because we'd like to rehash when the table is filled with tombstones and/or
// full slots.
//
// GrowthInfo also stores a bit that encodes whether table may have any
// deleted slots.
// Most of the tables (>95%) have no deleted slots, so some functions can
// be more efficient with this information.
//
// Callers can also force a rehash via the standard `rehash(0)`,
// which will recompute this value as a side-effect.
//
// See also `CapacityToGrowth()`.
class GrowthInfo {
 public:
  // Leaves data member uninitialized.
  GrowthInfo() = default;

  // Initializes the GrowthInfo assuming we can grow `growth_left` elements
  // and there are no kDeleted slots in the table.
  void InitGrowthLeftNoDeleted(size_t growth_left) {
    growth_left_info_ = growth_left;
  }

  // Overwrites single full slot with an empty slot.
  void OverwriteFullAsEmpty() { ++growth_left_info_; }

  // Overwrites single empty slot with a full slot.
  void OverwriteEmptyAsFull() {
    assert(GetGrowthLeft() > 0);
    --growth_left_info_;
  }

  // Overwrites several empty slots with full slots.
  void OverwriteManyEmptyAsFull(size_t cnt) {
    assert(GetGrowthLeft() >= cnt);
    growth_left_info_ -= cnt;
  }

  // Overwrites specified control element with full slot.
  void OverwriteControlAsFull(ctrl_t ctrl) {
    assert(GetGrowthLeft() >= static_cast<size_t>(IsEmpty(ctrl)));
    growth_left_info_ -= static_cast<size_t>(IsEmpty(ctrl));
  }

  // Overwrites single full slot with a deleted slot.
  void OverwriteFullAsDeleted() { growth_left_info_ |= kDeletedBit; }

  // Returns true if table satisfies two properties:
  // 1. Guaranteed to have no kDeleted slots.
  // 2. There is a place for at least one element to grow.
  bool HasNoDeletedAndGrowthLeft() const {
    return static_cast<std::make_signed_t<size_t>>(growth_left_info_) > 0;
  }

  // Returns true if the table satisfies two properties:
  // 1. Guaranteed to have no kDeleted slots.
  // 2. There is no growth left.
  bool HasNoGrowthLeftAndNoDeleted() const { return growth_left_info_ == 0; }

  // Returns true if table guaranteed to have no k
  bool HasNoDeleted() const {
    return static_cast<std::make_signed_t<size_t>>(growth_left_info_) >= 0;
  }

  // Returns the number of elements left to grow.
  size_t GetGrowthLeft() const { return growth_left_info_ & kGrowthLeftMask; }

 private:
  static constexpr size_t kGrowthLeftMask = ((~size_t{}) >> 1);
  static constexpr size_t kDeletedBit = ~kGrowthLeftMask;
  // Topmost bit signal whenever there are deleted slots.
  size_t growth_left_info_;
};

static_assert(sizeof(GrowthInfo) == sizeof(size_t), "");
static_assert(alignof(GrowthInfo) == alignof(size_t), "");

// Returns whether `n` is a valid capacity (i.e., number of slots).
//
// A valid capacity is a non-zero integer `2^m - 1`.
inline bool IsValidCapacity(size_t n) { return ((n + 1) & n) == 0 && n > 0; }

// Returns the number of "cloned control bytes".
//
// This is the number of control bytes that are present both at the beginning
// of the control byte array and at the end, such that we can create a
// `Group::kWidth`-width probe window starting from any control byte.
constexpr size_t NumClonedBytes() { return Group::kWidth - 1; }

// Returns the number of control bytes including cloned.
constexpr size_t NumControlBytes(size_t capacity) {
  return capacity + 1 + NumClonedBytes();
}

// Computes the offset from the start of the backing allocation of control.
// infoz and growth_info are stored at the beginning of the backing array.
inline static size_t ControlOffset(bool has_infoz) {
  return (has_infoz ? sizeof(HashtablezInfoHandle) : 0) + sizeof(GrowthInfo);
}

// Helper class for computing offsets and allocation size of hash set fields.
class RawHashSetLayout {
 public:
  explicit RawHashSetLayout(size_t capacity, size_t slot_align, bool has_infoz)
      : capacity_(capacity),
        control_offset_(ControlOffset(has_infoz)),
        generation_offset_(control_offset_ + NumControlBytes(capacity)),
        slot_offset_(
            (generation_offset_ + NumGenerationBytes() + slot_align - 1) &
            (~slot_align + 1)) {
    assert(IsValidCapacity(capacity));
  }

  // Returns the capacity of a table.
  size_t capacity() const { return capacity_; }

  // Returns precomputed offset from the start of the backing allocation of
  // control.
  size_t control_offset() const { return control_offset_; }

  // Given the capacity of a table, computes the offset (from the start of the
  // backing allocation) of the generation counter (if it exists).
  size_t generation_offset() const { return generation_offset_; }

  // Given the capacity of a table, computes the offset (from the start of the
  // backing allocation) at which the slots begin.
  size_t slot_offset() const { return slot_offset_; }

  // Given the capacity of a table, computes the total size of the backing
  // array.
  size_t alloc_size(size_t slot_size) const {
    return slot_offset_ + capacity_ * slot_size;
  }

 private:
  size_t capacity_;
  size_t control_offset_;
  size_t generation_offset_;
  size_t slot_offset_;
};

struct HashtableFreeFunctionsAccess;

// We only allow a maximum of 1 SOO element, which makes the implementation
// much simpler. Complications with multiple SOO elements include:
// - Satisfying the guarantee that erasing one element doesn't invalidate
//   iterators to other elements means we would probably need actual SOO
//   control bytes.
// - In order to prevent user code from depending on iteration order for small
//   tables, we would need to randomize the iteration order somehow.
constexpr size_t SooCapacity() { return 1; }
// Sentinel type to indicate SOO CommonFields construction.
struct soo_tag_t {};
// Sentinel type to indicate SOO CommonFields construction with full size.
struct full_soo_tag_t {};

// Suppress erroneous uninitialized memory errors on GCC. For example, GCC
// thinks that the call to slot_array() in find_or_prepare_insert() is reading
// uninitialized memory, but slot_array is only called there when the table is
// non-empty and this memory is initialized when the table is non-empty.
#if !defined(__clang__) && defined(__GNUC__)
#define Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(x)                    \
  _Pragma("GCC diagnostic push")                                   \
      _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")  \
          _Pragma("GCC diagnostic ignored \"-Wuninitialized\"") x; \
  _Pragma("GCC diagnostic pop")
#define Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(x) \
  Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(return x)
#else
#define Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(x) x
#define Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(x) return x
#endif

// This allows us to work around an uninitialized memory warning when
// constructing begin() iterators in empty hashtables.
union MaybeInitializedPtr {
  void* get() const { Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(p); }
  void set(void* ptr) { p = ptr; }

  void* p;
};

struct HeapPtrs {
  HeapPtrs() = default;
  explicit HeapPtrs(ctrl_t* c) : control(c) {}

  // The control bytes (and, also, a pointer near to the base of the backing
  // array).
  //
  // This contains `capacity + 1 + NumClonedBytes()` entries, even
  // when the table is empty (hence EmptyGroup).
  //
  // Note that growth_info is stored immediately before this pointer.
  // May be uninitialized for SOO tables.
  ctrl_t* control;

  // The beginning of the slots, located at `SlotOffset()` bytes after
  // `control`. May be uninitialized for empty tables.
  // Note: we can't use `slots` because Qt defines "slots" as a macro.
  MaybeInitializedPtr slot_array;
};

// Manages the backing array pointers or the SOO slot. When raw_hash_set::is_soo
// is true, the SOO slot is stored in `soo_data`. Otherwise, we use `heap`.
union HeapOrSoo {
  HeapOrSoo() = default;
  explicit HeapOrSoo(ctrl_t* c) : heap(c) {}

  ctrl_t*& control() {
    Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(heap.control);
  }
  ctrl_t* control() const {
    Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(heap.control);
  }
  MaybeInitializedPtr& slot_array() {
    Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(heap.slot_array);
  }
  MaybeInitializedPtr slot_array() const {
    Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(heap.slot_array);
  }
  void* get_soo_data() {
    Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(soo_data);
  }
  const void* get_soo_data() const {
    Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(soo_data);
  }

  HeapPtrs heap;
  unsigned char soo_data[sizeof(HeapPtrs)];
};

// CommonFields hold the fields in raw_hash_set that do not depend
// on template parameters. This allows us to conveniently pass all
// of this state to helper functions as a single argument.
class CommonFields : public CommonFieldsGenerationInfo {
 public:
  CommonFields() : capacity_(0), size_(0), heap_or_soo_(EmptyGroup()) {}
  explicit CommonFields(soo_tag_t) : capacity_(SooCapacity()), size_(0) {}
  explicit CommonFields(full_soo_tag_t)
      : capacity_(SooCapacity()), size_(size_t{1} << HasInfozShift()) {}

  // Not copyable
  CommonFields(const CommonFields&) = delete;
  CommonFields& operator=(const CommonFields&) = delete;

  // Movable
  CommonFields(CommonFields&& that) = default;
  CommonFields& operator=(CommonFields&&) = default;

  template <bool kSooEnabled>
  static CommonFields CreateDefault() {
    return kSooEnabled ? CommonFields{soo_tag_t{}} : CommonFields{};
  }

  // The inline data for SOO is written on top of control_/slots_.
  const void* soo_data() const { return heap_or_soo_.get_soo_data(); }
  void* soo_data() { return heap_or_soo_.get_soo_data(); }

  HeapOrSoo heap_or_soo() const { return heap_or_soo_; }
  const HeapOrSoo& heap_or_soo_ref() const { return heap_or_soo_; }

  ctrl_t* control() const { return heap_or_soo_.control(); }
  void set_control(ctrl_t* c) { heap_or_soo_.control() = c; }
  void* backing_array_start() const {
    // growth_info (and maybe infoz) is stored before control bytes.
    assert(reinterpret_cast<uintptr_t>(control()) % alignof(size_t) == 0);
    return control() - ControlOffset(has_infoz());
  }

  // Note: we can't use slots() because Qt defines "slots" as a macro.
  void* slot_array() const { return heap_or_soo_.slot_array().get(); }
  MaybeInitializedPtr slots_union() const { return heap_or_soo_.slot_array(); }
  void set_slots(void* s) { heap_or_soo_.slot_array().set(s); }

  // The number of filled slots.
  size_t size() const { return size_ >> HasInfozShift(); }
  void set_size(size_t s) {
    size_ = (s << HasInfozShift()) | (size_ & HasInfozMask());
  }
  void set_empty_soo() {
    AssertInSooMode();
    size_ = 0;
  }
  void set_full_soo() {
    AssertInSooMode();
    size_ = size_t{1} << HasInfozShift();
  }
  void increment_size() {
    assert(size() < capacity());
    size_ += size_t{1} << HasInfozShift();
  }
  void decrement_size() {
    assert(size() > 0);
    size_ -= size_t{1} << HasInfozShift();
  }

  // The total number of available slots.
  size_t capacity() const { return capacity_; }
  void set_capacity(size_t c) {
    assert(c == 0 || IsValidCapacity(c));
    capacity_ = c;
  }

  // The number of slots we can still fill without needing to rehash.
  // This is stored in the heap allocation before the control bytes.
  // TODO(b/289225379): experiment with moving growth_info back inline to
  // increase room for SOO.
  size_t growth_left() const { return growth_info().GetGrowthLeft(); }

  GrowthInfo& growth_info() {
    auto* gl_ptr = reinterpret_cast<GrowthInfo*>(control()) - 1;
    assert(reinterpret_cast<uintptr_t>(gl_ptr) % alignof(GrowthInfo) == 0);
    return *gl_ptr;
  }
  GrowthInfo growth_info() const {
    return const_cast<CommonFields*>(this)->growth_info();
  }

  bool has_infoz() const {
    return Y_ABSL_PREDICT_FALSE((size_ & HasInfozMask()) != 0);
  }
  void set_has_infoz(bool has_infoz) {
    size_ = (size() << HasInfozShift()) | static_cast<size_t>(has_infoz);
  }

  HashtablezInfoHandle infoz() {
    return has_infoz()
               ? *reinterpret_cast<HashtablezInfoHandle*>(backing_array_start())
               : HashtablezInfoHandle();
  }
  void set_infoz(HashtablezInfoHandle infoz) {
    assert(has_infoz());
    *reinterpret_cast<HashtablezInfoHandle*>(backing_array_start()) = infoz;
  }

  bool should_rehash_for_bug_detection_on_insert() const {
    return CommonFieldsGenerationInfo::
        should_rehash_for_bug_detection_on_insert(control(), capacity());
  }
  bool should_rehash_for_bug_detection_on_move() const {
    return CommonFieldsGenerationInfo::should_rehash_for_bug_detection_on_move(
        control(), capacity());
  }
  void reset_reserved_growth(size_t reservation) {
    CommonFieldsGenerationInfo::reset_reserved_growth(reservation, size());
  }

  // The size of the backing array allocation.
  size_t alloc_size(size_t slot_size, size_t slot_align) const {
    return RawHashSetLayout(capacity(), slot_align, has_infoz())
        .alloc_size(slot_size);
  }

  // Move fields other than heap_or_soo_.
  void move_non_heap_or_soo_fields(CommonFields& that) {
    static_cast<CommonFieldsGenerationInfo&>(*this) =
        std::move(static_cast<CommonFieldsGenerationInfo&>(that));
    capacity_ = that.capacity_;
    size_ = that.size_;
  }

  // Returns the number of control bytes set to kDeleted. For testing only.
  size_t TombstonesCount() const {
    return static_cast<size_t>(
        std::count(control(), control() + capacity(), ctrl_t::kDeleted));
  }

 private:
  // We store the has_infoz bit in the lowest bit of size_.
  static constexpr size_t HasInfozShift() { return 1; }
  static constexpr size_t HasInfozMask() {
    return (size_t{1} << HasInfozShift()) - 1;
  }

  // We can't assert that SOO is enabled because we don't have SooEnabled(), but
  // we assert what we can.
  void AssertInSooMode() const {
    assert(capacity() == SooCapacity());
    assert(!has_infoz());
  }

  // The number of slots in the backing array. This is always 2^N-1 for an
  // integer N. NOTE: we tried experimenting with compressing the capacity and
  // storing it together with size_: (a) using 6 bits to store the corresponding
  // power (N in 2^N-1), and (b) storing 2^N as the most significant bit of
  // size_ and storing size in the low bits. Both of these experiments were
  // regressions, presumably because we need capacity to do find operations.
  size_t capacity_;

  // The size and also has one bit that stores whether we have infoz.
  // TODO(b/289225379): we could put size_ into HeapOrSoo and make capacity_
  // encode the size in SOO case. We would be making size()/capacity() more
  // expensive in order to have more SOO space.
  size_t size_;

  // Either the control/slots pointers or the SOO slot.
  HeapOrSoo heap_or_soo_;
};

template <class Policy, class Hash, class Eq, class Alloc>
class raw_hash_set;

// Returns the next valid capacity after `n`.
inline size_t NextCapacity(size_t n) {
  assert(IsValidCapacity(n) || n == 0);
  return n * 2 + 1;
}

// Applies the following mapping to every byte in the control array:
//   * kDeleted -> kEmpty
//   * kEmpty -> kEmpty
//   * _ -> kDeleted
// PRECONDITION:
//   IsValidCapacity(capacity)
//   ctrl[capacity] == ctrl_t::kSentinel
//   ctrl[i] != ctrl_t::kSentinel for all i < capacity
void ConvertDeletedToEmptyAndFullToDeleted(ctrl_t* ctrl, size_t capacity);

// Converts `n` into the next valid capacity, per `IsValidCapacity`.
inline size_t NormalizeCapacity(size_t n) {
  return n ? ~size_t{} >> countl_zero(n) : 1;
}

// General notes on capacity/growth methods below:
// - We use 7/8th as maximum load factor. For 16-wide groups, that gives an
//   average of two empty slots per group.
// - For (capacity+1) >= Group::kWidth, growth is 7/8*capacity.
// - For (capacity+1) < Group::kWidth, growth == capacity. In this case, we
//   never need to probe (the whole table fits in one group) so we don't need a
//   load factor less than 1.

// Given `capacity`, applies the load factor; i.e., it returns the maximum
// number of values we should put into the table before a resizing rehash.
inline size_t CapacityToGrowth(size_t capacity) {
  assert(IsValidCapacity(capacity));
  // `capacity*7/8`
  if (Group::kWidth == 8 && capacity == 7) {
    // x-x/8 does not work when x==7.
    return 6;
  }
  return capacity - capacity / 8;
}

// Given `growth`, "unapplies" the load factor to find how large the capacity
// should be to stay within the load factor.
//
// This might not be a valid capacity and `NormalizeCapacity()` should be
// called on this.
inline size_t GrowthToLowerboundCapacity(size_t growth) {
  // `growth*8/7`
  if (Group::kWidth == 8 && growth == 7) {
    // x+(x-1)/7 does not work when x==7.
    return 8;
  }
  return growth + static_cast<size_t>((static_cast<int64_t>(growth) - 1) / 7);
}

template <class InputIter>
size_t SelectBucketCountForIterRange(InputIter first, InputIter last,
                                     size_t bucket_count) {
  if (bucket_count != 0) {
    return bucket_count;
  }
  using InputIterCategory =
      typename std::iterator_traits<InputIter>::iterator_category;
  if (std::is_base_of<std::random_access_iterator_tag,
                      InputIterCategory>::value) {
    return GrowthToLowerboundCapacity(
        static_cast<size_t>(std::distance(first, last)));
  }
  return 0;
}

constexpr bool SwisstableDebugEnabled() {
#if defined(Y_ABSL_SWISSTABLE_ENABLE_GENERATIONS) || \
    Y_ABSL_OPTION_HARDENED == 1 || !defined(NDEBUG)
  return true;
#else
  return false;
#endif
}

inline void AssertIsFull(const ctrl_t* ctrl, GenerationType generation,
                         const GenerationType* generation_ptr,
                         const char* operation) {
  if (!SwisstableDebugEnabled()) return;
  // `SwisstableDebugEnabled()` is also true for release builds with hardening
  // enabled. To minimize their impact in those builds:
  // - use `Y_ABSL_PREDICT_FALSE()` to provide a compiler hint for code layout
  // - use `Y_ABSL_RAW_LOG()` with a format string to reduce code size and improve
  //   the chances that the hot paths will be inlined.
  if (Y_ABSL_PREDICT_FALSE(ctrl == nullptr)) {
    Y_ABSL_RAW_LOG(FATAL, "%s called on end() iterator.", operation);
  }
  if (Y_ABSL_PREDICT_FALSE(ctrl == EmptyGroup())) {
    Y_ABSL_RAW_LOG(FATAL, "%s called on default-constructed iterator.",
                 operation);
  }
  if (SwisstableGenerationsEnabled()) {
    if (Y_ABSL_PREDICT_FALSE(generation != *generation_ptr)) {
      Y_ABSL_RAW_LOG(FATAL,
                   "%s called on invalid iterator. The table could have "
                   "rehashed or moved since this iterator was initialized.",
                   operation);
    }
    if (Y_ABSL_PREDICT_FALSE(!IsFull(*ctrl))) {
      Y_ABSL_RAW_LOG(
          FATAL,
          "%s called on invalid iterator. The element was likely erased.",
          operation);
    }
  } else {
    if (Y_ABSL_PREDICT_FALSE(!IsFull(*ctrl))) {
      Y_ABSL_RAW_LOG(
          FATAL,
          "%s called on invalid iterator. The element might have been erased "
          "or the table might have rehashed. Consider running with "
          "--config=asan to diagnose rehashing issues.",
          operation);
    }
  }
}

// Note that for comparisons, null/end iterators are valid.
inline void AssertIsValidForComparison(const ctrl_t* ctrl,
                                       GenerationType generation,
                                       const GenerationType* generation_ptr) {
  if (!SwisstableDebugEnabled()) return;
  const bool ctrl_is_valid_for_comparison =
      ctrl == nullptr || ctrl == EmptyGroup() || IsFull(*ctrl);
  if (SwisstableGenerationsEnabled()) {
    if (Y_ABSL_PREDICT_FALSE(generation != *generation_ptr)) {
      Y_ABSL_RAW_LOG(FATAL,
                   "Invalid iterator comparison. The table could have rehashed "
                   "or moved since this iterator was initialized.");
    }
    if (Y_ABSL_PREDICT_FALSE(!ctrl_is_valid_for_comparison)) {
      Y_ABSL_RAW_LOG(
          FATAL, "Invalid iterator comparison. The element was likely erased.");
    }
  } else {
    Y_ABSL_HARDENING_ASSERT(
        ctrl_is_valid_for_comparison &&
        "Invalid iterator comparison. The element might have been erased or "
        "the table might have rehashed. Consider running with --config=asan to "
        "diagnose rehashing issues.");
  }
}

// If the two iterators come from the same container, then their pointers will
// interleave such that ctrl_a <= ctrl_b < slot_a <= slot_b or vice/versa.
// Note: we take slots by reference so that it's not UB if they're uninitialized
// as long as we don't read them (when ctrl is null).
inline bool AreItersFromSameContainer(const ctrl_t* ctrl_a,
                                      const ctrl_t* ctrl_b,
                                      const void* const& slot_a,
                                      const void* const& slot_b) {
  // If either control byte is null, then we can't tell.
  if (ctrl_a == nullptr || ctrl_b == nullptr) return true;
  const bool a_is_soo = IsSooControl(ctrl_a);
  if (a_is_soo != IsSooControl(ctrl_b)) return false;
  if (a_is_soo) return slot_a == slot_b;

  const void* low_slot = slot_a;
  const void* hi_slot = slot_b;
  if (ctrl_a > ctrl_b) {
    std::swap(ctrl_a, ctrl_b);
    std::swap(low_slot, hi_slot);
  }
  return ctrl_b < low_slot && low_slot <= hi_slot;
}

// Asserts that two iterators come from the same container.
// Note: we take slots by reference so that it's not UB if they're uninitialized
// as long as we don't read them (when ctrl is null).
inline void AssertSameContainer(const ctrl_t* ctrl_a, const ctrl_t* ctrl_b,
                                const void* const& slot_a,
                                const void* const& slot_b,
                                const GenerationType* generation_ptr_a,
                                const GenerationType* generation_ptr_b) {
  if (!SwisstableDebugEnabled()) return;
  // `SwisstableDebugEnabled()` is also true for release builds with hardening
  // enabled. To minimize their impact in those builds:
  // - use `Y_ABSL_PREDICT_FALSE()` to provide a compiler hint for code layout
  // - use `Y_ABSL_RAW_LOG()` with a format string to reduce code size and improve
  //   the chances that the hot paths will be inlined.

  // fail_if(is_invalid, message) crashes when is_invalid is true and provides
  // an error message based on `message`.
  const auto fail_if = [](bool is_invalid, const char* message) {
    if (Y_ABSL_PREDICT_FALSE(is_invalid)) {
      Y_ABSL_RAW_LOG(FATAL, "Invalid iterator comparison. %s", message);
    }
  };

  const bool a_is_default = ctrl_a == EmptyGroup();
  const bool b_is_default = ctrl_b == EmptyGroup();
  if (a_is_default && b_is_default) return;
  fail_if(a_is_default != b_is_default,
          "Comparing default-constructed hashtable iterator with a "
          "non-default-constructed hashtable iterator.");

  if (SwisstableGenerationsEnabled()) {
    if (Y_ABSL_PREDICT_TRUE(generation_ptr_a == generation_ptr_b)) return;
    // Users don't need to know whether the tables are SOO so don't mention SOO
    // in the debug message.
    const bool a_is_soo = IsSooControl(ctrl_a);
    const bool b_is_soo = IsSooControl(ctrl_b);
    fail_if(a_is_soo != b_is_soo || (a_is_soo && b_is_soo),
            "Comparing iterators from different hashtables.");

    const bool a_is_empty = IsEmptyGeneration(generation_ptr_a);
    const bool b_is_empty = IsEmptyGeneration(generation_ptr_b);
    fail_if(a_is_empty != b_is_empty,
            "Comparing an iterator from an empty hashtable with an iterator "
            "from a non-empty hashtable.");
    fail_if(a_is_empty && b_is_empty,
            "Comparing iterators from different empty hashtables.");

    const bool a_is_end = ctrl_a == nullptr;
    const bool b_is_end = ctrl_b == nullptr;
    fail_if(a_is_end || b_is_end,
            "Comparing iterator with an end() iterator from a different "
            "hashtable.");
    fail_if(true, "Comparing non-end() iterators from different hashtables.");
  } else {
    Y_ABSL_HARDENING_ASSERT(
        AreItersFromSameContainer(ctrl_a, ctrl_b, slot_a, slot_b) &&
        "Invalid iterator comparison. The iterators may be from different "
        "containers or the container might have rehashed or moved. Consider "
        "running with --config=asan to diagnose issues.");
  }
}

struct FindInfo {
  size_t offset;
  size_t probe_length;
};

// Whether a table is "small". A small table fits entirely into a probing
// group, i.e., has a capacity < `Group::kWidth`.
//
// In small mode we are able to use the whole capacity. The extra control
// bytes give us at least one "empty" control byte to stop the iteration.
// This is important to make 1 a valid capacity.
//
// In small mode only the first `capacity` control bytes after the sentinel
// are valid. The rest contain dummy ctrl_t::kEmpty values that do not
// represent a real slot. This is important to take into account on
// `find_first_non_full()`, where we never try
// `ShouldInsertBackwards()` for small tables.
inline bool is_small(size_t capacity) { return capacity < Group::kWidth - 1; }

// Whether a table fits entirely into a probing group.
// Arbitrary order of elements in such tables is correct.
inline bool is_single_group(size_t capacity) {
  return capacity <= Group::kWidth;
}

// Begins a probing operation on `common.control`, using `hash`.
inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, const size_t capacity,
                                      size_t hash) {
  return probe_seq<Group::kWidth>(H1(hash, ctrl), capacity);
}
inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) {
  return probe(common.control(), common.capacity(), hash);
}

// Probes an array of control bits using a probe sequence derived from `hash`,
// and returns the offset corresponding to the first deleted or empty slot.
//
// Behavior when the entire table is full is undefined.
//
// NOTE: this function must work with tables having both empty and deleted
// slots in the same group. Such tables appear during `erase()`.
template <typename = void>
inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) {
  auto seq = probe(common, hash);
  const ctrl_t* ctrl = common.control();
  if (IsEmptyOrDeleted(ctrl[seq.offset()]) &&
      !ShouldInsertBackwards(common.capacity(), hash, ctrl)) {
    return {seq.offset(), /*probe_length=*/0};
  }
  while (true) {
    GroupFullEmptyOrDeleted g{ctrl + seq.offset()};
    auto mask = g.MaskEmptyOrDeleted();
    if (mask) {
      return {
          seq.offset(GetInsertionOffset(mask, common.capacity(), hash, ctrl)),
          seq.index()};
    }
    seq.next();
    assert(seq.index() <= common.capacity() && "full table!");
  }
}

// Extern template for inline function keep possibility of inlining.
// When compiler decided to not inline, no symbols will be added to the
// corresponding translation unit.
extern template FindInfo find_first_non_full(const CommonFields&, size_t);

// Non-inlined version of find_first_non_full for use in less
// performance critical routines.
FindInfo find_first_non_full_outofline(const CommonFields&, size_t);

inline void ResetGrowthLeft(CommonFields& common) {
  common.growth_info().InitGrowthLeftNoDeleted(
      CapacityToGrowth(common.capacity()) - common.size());
}

// Sets `ctrl` to `{kEmpty, kSentinel, ..., kEmpty}`, marking the entire
// array as marked as empty.
inline void ResetCtrl(CommonFields& common, size_t slot_size) {
  const size_t capacity = common.capacity();
  ctrl_t* ctrl = common.control();
  std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty),
              capacity + 1 + NumClonedBytes());
  ctrl[capacity] = ctrl_t::kSentinel;
  SanitizerPoisonMemoryRegion(common.slot_array(), slot_size * capacity);
}

// Sets sanitizer poisoning for slot corresponding to control byte being set.
inline void DoSanitizeOnSetCtrl(const CommonFields& c, size_t i, ctrl_t h,
                                size_t slot_size) {
  assert(i < c.capacity());
  auto* slot_i = static_cast<const char*>(c.slot_array()) + i * slot_size;
  if (IsFull(h)) {
    SanitizerUnpoisonMemoryRegion(slot_i, slot_size);
  } else {
    SanitizerPoisonMemoryRegion(slot_i, slot_size);
  }
}

// Sets `ctrl[i]` to `h`.
//
// Unlike setting it directly, this function will perform bounds checks and
// mirror the value to the cloned tail if necessary.
inline void SetCtrl(const CommonFields& c, size_t i, ctrl_t h,
                    size_t slot_size) {
  DoSanitizeOnSetCtrl(c, i, h, slot_size);
  ctrl_t* ctrl = c.control();
  ctrl[i] = h;
  ctrl[((i - NumClonedBytes()) & c.capacity()) +
       (NumClonedBytes() & c.capacity())] = h;
}
// Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`.
inline void SetCtrl(const CommonFields& c, size_t i, h2_t h, size_t slot_size) {
  SetCtrl(c, i, static_cast<ctrl_t>(h), slot_size);
}

// Like SetCtrl, but in a single group table, we can save some operations when
// setting the cloned control byte.
inline void SetCtrlInSingleGroupTable(const CommonFields& c, size_t i, ctrl_t h,
                                      size_t slot_size) {
  assert(is_single_group(c.capacity()));
  DoSanitizeOnSetCtrl(c, i, h, slot_size);
  ctrl_t* ctrl = c.control();
  ctrl[i] = h;
  ctrl[i + c.capacity() + 1] = h;
}
// Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`.
inline void SetCtrlInSingleGroupTable(const CommonFields& c, size_t i, h2_t h,
                                      size_t slot_size) {
  SetCtrlInSingleGroupTable(c, i, static_cast<ctrl_t>(h), slot_size);
}

// growth_info (which is a size_t) is stored with the backing array.
constexpr size_t BackingArrayAlignment(size_t align_of_slot) {
  return (std::max)(align_of_slot, alignof(GrowthInfo));
}

// Returns the address of the ith slot in slots where each slot occupies
// slot_size.
inline void* SlotAddress(void* slot_array, size_t slot, size_t slot_size) {
  return static_cast<void*>(static_cast<char*>(slot_array) +
                            (slot * slot_size));
}

// Iterates over all full slots and calls `cb(const ctrl_t*, SlotType*)`.
// No insertion to the table allowed during Callback call.
// Erasure is allowed only for the element passed to the callback.
template <class SlotType, class Callback>
Y_ABSL_ATTRIBUTE_ALWAYS_INLINE inline void IterateOverFullSlots(
    const CommonFields& c, SlotType* slot, Callback cb) {
  const size_t cap = c.capacity();
  const ctrl_t* ctrl = c.control();
  if (is_small(cap)) {
    // Mirrored/cloned control bytes in small table are also located in the
    // first group (starting from position 0). We are taking group from position
    // `capacity` in order to avoid duplicates.

    // Small tables capacity fits into portable group, where
    // GroupPortableImpl::MaskFull is more efficient for the
    // capacity <= GroupPortableImpl::kWidth.
    assert(cap <= GroupPortableImpl::kWidth &&
           "unexpectedly large small capacity");
    static_assert(Group::kWidth >= GroupPortableImpl::kWidth,
                  "unexpected group width");
    // Group starts from kSentinel slot, so indices in the mask will
    // be increased by 1.
    const auto mask = GroupPortableImpl(ctrl + cap).MaskFull();
    --ctrl;
    --slot;
    for (uint32_t i : mask) {
      cb(ctrl + i, slot + i);
    }
    return;
  }
  size_t remaining = c.size();
  Y_ABSL_ATTRIBUTE_UNUSED const size_t original_size_for_assert = remaining;
  while (remaining != 0) {
    for (uint32_t i : GroupFullEmptyOrDeleted(ctrl).MaskFull()) {
      assert(IsFull(ctrl[i]) && "hash table was modified unexpectedly");
      cb(ctrl + i, slot + i);
      --remaining;
    }
    ctrl += Group::kWidth;
    slot += Group::kWidth;
    assert((remaining == 0 || *(ctrl - 1) != ctrl_t::kSentinel) &&
           "hash table was modified unexpectedly");
  }
  // NOTE: erasure of the current element is allowed in callback for
  // y_absl::erase_if specialization. So we use `>=`.
  assert(original_size_for_assert >= c.size() &&
         "hash table was modified unexpectedly");
}

template <typename CharAlloc>
constexpr bool ShouldSampleHashtablezInfo() {
  // Folks with custom allocators often make unwarranted assumptions about the
  // behavior of their classes vis-a-vis trivial destructability and what
  // calls they will or won't make.  Avoid sampling for people with custom
  // allocators to get us out of this mess.  This is not a hard guarantee but
  // a workaround while we plan the exact guarantee we want to provide.
  return std::is_same<CharAlloc, std::allocator<char>>::value;
}

template <bool kSooEnabled>
HashtablezInfoHandle SampleHashtablezInfo(size_t sizeof_slot, size_t sizeof_key,
                                          size_t sizeof_value,
                                          size_t old_capacity, bool was_soo,
                                          HashtablezInfoHandle forced_infoz,
                                          CommonFields& c) {
  if (forced_infoz.IsSampled()) return forced_infoz;
  // In SOO, we sample on the first insertion so if this is an empty SOO case
  // (e.g. when reserve is called), then we still need to sample.
  if (kSooEnabled && was_soo && c.size() == 0) {
    return Sample(sizeof_slot, sizeof_key, sizeof_value, SooCapacity());
  }
  // For non-SOO cases, we sample whenever the capacity is increasing from zero
  // to non-zero.
  if (!kSooEnabled && old_capacity == 0) {
    return Sample(sizeof_slot, sizeof_key, sizeof_value, 0);
  }
  return c.infoz();
}

// Helper class to perform resize of the hash set.
//
// It contains special optimizations for small group resizes.
// See GrowIntoSingleGroupShuffleControlBytes for details.
class HashSetResizeHelper {
 public:
  explicit HashSetResizeHelper(CommonFields& c, bool was_soo, bool had_soo_slot,
                               HashtablezInfoHandle forced_infoz)
      : old_capacity_(c.capacity()),
        had_infoz_(c.has_infoz()),
        was_soo_(was_soo),
        had_soo_slot_(had_soo_slot),
        forced_infoz_(forced_infoz) {}

  // Optimized for small groups version of `find_first_non_full`.
  // Beneficial only right after calling `raw_hash_set::resize`.
  // It is safe to call in case capacity is big or was not changed, but there
  // will be no performance benefit.
  // It has implicit assumption that `resize` will call
  // `GrowSizeIntoSingleGroup*` in case `IsGrowingIntoSingleGroupApplicable`.
  // Falls back to `find_first_non_full` in case of big groups.
  static FindInfo FindFirstNonFullAfterResize(const CommonFields& c,
                                              size_t old_capacity,
                                              size_t hash) {
    if (!IsGrowingIntoSingleGroupApplicable(old_capacity, c.capacity())) {
      return find_first_non_full(c, hash);
    }
    // Find a location for the new element non-deterministically.
    // Note that any position is correct.
    // It will located at `half_old_capacity` or one of the other
    // empty slots with approximately 50% probability each.
    size_t offset = probe(c, hash).offset();

    // Note that we intentionally use unsigned int underflow.
    if (offset - (old_capacity + 1) >= old_capacity) {
      // Offset fall on kSentinel or into the mostly occupied first half.
      offset = old_capacity / 2;
    }
    assert(IsEmpty(c.control()[offset]));
    return FindInfo{offset, 0};
  }

  HeapOrSoo& old_heap_or_soo() { return old_heap_or_soo_; }
  void* old_soo_data() { return old_heap_or_soo_.get_soo_data(); }
  ctrl_t* old_ctrl() const {
    assert(!was_soo_);
    return old_heap_or_soo_.control();
  }
  void* old_slots() const {
    assert(!was_soo_);
    return old_heap_or_soo_.slot_array().get();
  }
  size_t old_capacity() const { return old_capacity_; }

  // Returns the index of the SOO slot when growing from SOO to non-SOO in a
  // single group. See also InitControlBytesAfterSoo(). It's important to use
  // index 1 so that when resizing from capacity 1 to 3, we can still have
  // random iteration order between the first two inserted elements.
  // I.e. it allows inserting the second element at either index 0 or 2.
  static size_t SooSlotIndex() { return 1; }

  // Allocates a backing array for the hashtable.
  // Reads `capacity` and updates all other fields based on the result of
  // the allocation.
  //
  // It also may do the following actions:
  // 1. initialize control bytes
  // 2. initialize slots
  // 3. deallocate old slots.
  //
  // We are bundling a lot of functionality
  // in one Y_ABSL_ATTRIBUTE_NOINLINE function in order to minimize binary code
  // duplication in raw_hash_set<>::resize.
  //
  // `c.capacity()` must be nonzero.
  // POSTCONDITIONS:
  //  1. CommonFields is initialized.
  //
  //  if IsGrowingIntoSingleGroupApplicable && TransferUsesMemcpy
  //    Both control bytes and slots are fully initialized.
  //    old_slots are deallocated.
  //    infoz.RecordRehash is called.
  //
  //  if IsGrowingIntoSingleGroupApplicable && !TransferUsesMemcpy
  //    Control bytes are fully initialized.
  //    infoz.RecordRehash is called.
  //    GrowSizeIntoSingleGroup must be called to finish slots initialization.
  //
  //  if !IsGrowingIntoSingleGroupApplicable
  //    Control bytes are initialized to empty table via ResetCtrl.
  //    raw_hash_set<>::resize must insert elements regularly.
  //    infoz.RecordRehash is called if old_capacity == 0.
  //
  //  Returns IsGrowingIntoSingleGroupApplicable result to avoid recomputation.
  template <typename Alloc, size_t SizeOfSlot, bool TransferUsesMemcpy,
            bool SooEnabled, size_t AlignOfSlot>
  Y_ABSL_ATTRIBUTE_NOINLINE bool InitializeSlots(CommonFields& c, Alloc alloc,
                                               ctrl_t soo_slot_h2,
                                               size_t key_size,
                                               size_t value_size) {
    assert(c.capacity());
    HashtablezInfoHandle infoz =
        ShouldSampleHashtablezInfo<Alloc>()
            ? SampleHashtablezInfo<SooEnabled>(SizeOfSlot, key_size, value_size,
                                               old_capacity_, was_soo_,
                                               forced_infoz_, c)
            : HashtablezInfoHandle{};

    const bool has_infoz = infoz.IsSampled();
    RawHashSetLayout layout(c.capacity(), AlignOfSlot, has_infoz);
    char* mem = static_cast<char*>(Allocate<BackingArrayAlignment(AlignOfSlot)>(
        &alloc, layout.alloc_size(SizeOfSlot)));
    const GenerationType old_generation = c.generation();
    c.set_generation_ptr(
        reinterpret_cast<GenerationType*>(mem + layout.generation_offset()));
    c.set_generation(NextGeneration(old_generation));
    c.set_control(reinterpret_cast<ctrl_t*>(mem + layout.control_offset()));
    c.set_slots(mem + layout.slot_offset());
    ResetGrowthLeft(c);

    const bool grow_single_group =
        IsGrowingIntoSingleGroupApplicable(old_capacity_, layout.capacity());
    if (SooEnabled && was_soo_ && grow_single_group) {
      InitControlBytesAfterSoo(c.control(), soo_slot_h2, layout.capacity());
      if (TransferUsesMemcpy && had_soo_slot_) {
        TransferSlotAfterSoo(c, SizeOfSlot);
      }
      // SooEnabled implies that old_capacity_ != 0.
    } else if ((SooEnabled || old_capacity_ != 0) && grow_single_group) {
      if (TransferUsesMemcpy) {
        GrowSizeIntoSingleGroupTransferable(c, SizeOfSlot);
        DeallocateOld<AlignOfSlot>(alloc, SizeOfSlot);
      } else {
        GrowIntoSingleGroupShuffleControlBytes(c.control(), layout.capacity());
      }
    } else {
      ResetCtrl(c, SizeOfSlot);
    }

    c.set_has_infoz(has_infoz);
    if (has_infoz) {
      infoz.RecordStorageChanged(c.size(), layout.capacity());
      if ((SooEnabled && was_soo_) || grow_single_group || old_capacity_ == 0) {
        infoz.RecordRehash(0);
      }
      c.set_infoz(infoz);
    }
    return grow_single_group;
  }

  // Relocates slots into new single group consistent with
  // GrowIntoSingleGroupShuffleControlBytes.
  //
  // PRECONDITIONS:
  // 1. GrowIntoSingleGroupShuffleControlBytes was already called.
  template <class PolicyTraits, class Alloc>
  void GrowSizeIntoSingleGroup(CommonFields& c, Alloc& alloc_ref) {
    assert(old_capacity_ < Group::kWidth / 2);
    assert(IsGrowingIntoSingleGroupApplicable(old_capacity_, c.capacity()));
    using slot_type = typename PolicyTraits::slot_type;
    assert(is_single_group(c.capacity()));

    auto* new_slots = static_cast<slot_type*>(c.slot_array());
    auto* old_slots_ptr = static_cast<slot_type*>(old_slots());

    size_t shuffle_bit = old_capacity_ / 2 + 1;
    for (size_t i = 0; i < old_capacity_; ++i) {
      if (IsFull(old_ctrl()[i])) {
        size_t new_i = i ^ shuffle_bit;
        SanitizerUnpoisonMemoryRegion(new_slots + new_i, sizeof(slot_type));
        PolicyTraits::transfer(&alloc_ref, new_slots + new_i,
                               old_slots_ptr + i);
      }
    }
    PoisonSingleGroupEmptySlots(c, sizeof(slot_type));
  }

  // Deallocates old backing array.
  template <size_t AlignOfSlot, class CharAlloc>
  void DeallocateOld(CharAlloc alloc_ref, size_t slot_size) {
    SanitizerUnpoisonMemoryRegion(old_slots(), slot_size * old_capacity_);
    auto layout = RawHashSetLayout(old_capacity_, AlignOfSlot, had_infoz_);
    Deallocate<BackingArrayAlignment(AlignOfSlot)>(
        &alloc_ref, old_ctrl() - layout.control_offset(),
        layout.alloc_size(slot_size));
  }

 private:
  // Returns true if `GrowSizeIntoSingleGroup` can be used for resizing.
  static bool IsGrowingIntoSingleGroupApplicable(size_t old_capacity,
                                                 size_t new_capacity) {
    // NOTE that `old_capacity < new_capacity` in order to have
    // `old_capacity < Group::kWidth / 2` to make faster copies of 8 bytes.
    return is_single_group(new_capacity) && old_capacity < new_capacity;
  }

  // Relocates control bytes and slots into new single group for
  // transferable objects.
  // Must be called only if IsGrowingIntoSingleGroupApplicable returned true.
  void GrowSizeIntoSingleGroupTransferable(CommonFields& c, size_t slot_size);

  // If there was an SOO slot and slots are transferable, transfers the SOO slot
  // into the new heap allocation. Must be called only if
  // IsGrowingIntoSingleGroupApplicable returned true.
  void TransferSlotAfterSoo(CommonFields& c, size_t slot_size);

  // Shuffle control bits deterministically to the next capacity.
  // Returns offset for newly added element with given hash.
  //
  // PRECONDITIONs:
  // 1. new_ctrl is allocated for new_capacity,
  //    but not initialized.
  // 2. new_capacity is a single group.
  //
  // All elements are transferred into the first `old_capacity + 1` positions
  // of the new_ctrl. Elements are rotated by `old_capacity_ / 2 + 1` positions
  // in order to change an order and keep it non deterministic.
  // Although rotation itself deterministic, position of the new added element
  // will be based on `H1` and is not deterministic.
  //
  // Examples:
  // S = kSentinel, E = kEmpty
  //
  // old_ctrl = SEEEEEEEE...
  // new_ctrl = ESEEEEEEE...
  //
  // old_ctrl = 0SEEEEEEE...
  // new_ctrl = E0ESE0EEE...
  //
  // old_ctrl = 012S012EEEEEEEEE...
  // new_ctrl = 2E01EEES2E01EEE...
  //
  // old_ctrl = 0123456S0123456EEEEEEEEEEE...
  // new_ctrl = 456E0123EEEEEES456E0123EEE...
  void GrowIntoSingleGroupShuffleControlBytes(ctrl_t* new_ctrl,
                                              size_t new_capacity) const;

  // If the table was SOO, initializes new control bytes. `h2` is the control
  // byte corresponding to the full slot. Must be called only if
  // IsGrowingIntoSingleGroupApplicable returned true.
  // Requires: `had_soo_slot_ || h2 == ctrl_t::kEmpty`.
  void InitControlBytesAfterSoo(ctrl_t* new_ctrl, ctrl_t h2,
                                size_t new_capacity);

  // Shuffle trivially transferable slots in the way consistent with
  // GrowIntoSingleGroupShuffleControlBytes.
  //
  // PRECONDITIONs:
  // 1. old_capacity must be non-zero.
  // 2. new_ctrl is fully initialized using
  //    GrowIntoSingleGroupShuffleControlBytes.
  // 3. new_slots is allocated and *not* poisoned.
  //
  // POSTCONDITIONS:
  // 1. new_slots are transferred from old_slots_ consistent with
  //    GrowIntoSingleGroupShuffleControlBytes.
  // 2. Empty new_slots are *not* poisoned.
  void GrowIntoSingleGroupShuffleTransferableSlots(void* new_slots,
                                                   size_t slot_size) const;

  // Poison empty slots that were transferred using the deterministic algorithm
  // described above.
  // PRECONDITIONs:
  // 1. new_ctrl is fully initialized using
  //    GrowIntoSingleGroupShuffleControlBytes.
  // 2. new_slots is fully initialized consistent with
  //    GrowIntoSingleGroupShuffleControlBytes.
  void PoisonSingleGroupEmptySlots(CommonFields& c, size_t slot_size) const {
    // poison non full items
    for (size_t i = 0; i < c.capacity(); ++i) {
      if (!IsFull(c.control()[i])) {
        SanitizerPoisonMemoryRegion(SlotAddress(c.slot_array(), i, slot_size),
                                    slot_size);
      }
    }
  }

  HeapOrSoo old_heap_or_soo_;
  size_t old_capacity_;
  bool had_infoz_;
  bool was_soo_;
  bool had_soo_slot_;
  // Either null infoz or a pre-sampled forced infoz for SOO tables.
  HashtablezInfoHandle forced_infoz_;
};

inline void PrepareInsertCommon(CommonFields& common) {
  common.increment_size();
  common.maybe_increment_generation_on_insert();
}

// Like prepare_insert, but for the case of inserting into a full SOO table.
size_t PrepareInsertAfterSoo(size_t hash, size_t slot_size,
                             CommonFields& common);

// PolicyFunctions bundles together some information for a particular
// raw_hash_set<T, ...> instantiation. This information is passed to
// type-erased functions that want to do small amounts of type-specific
// work.
struct PolicyFunctions {
  size_t slot_size;

  // Returns the pointer to the hash function stored in the set.
  const void* (*hash_fn)(const CommonFields& common);

  // Returns the hash of the pointed-to slot.
  size_t (*hash_slot)(const void* hash_fn, void* slot);

  // Transfers the contents of src_slot to dst_slot.
  void (*transfer)(void* set, void* dst_slot, void* src_slot);

  // Deallocates the backing store from common.
  void (*dealloc)(CommonFields& common, const PolicyFunctions& policy);

  // Resizes set to the new capacity.
  // Arguments are used as in raw_hash_set::resize_impl.
  void (*resize)(CommonFields& common, size_t new_capacity,
                 HashtablezInfoHandle forced_infoz);
};

// ClearBackingArray clears the backing array, either modifying it in place,
// or creating a new one based on the value of "reuse".
// REQUIRES: c.capacity > 0
void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy,
                       bool reuse, bool soo_enabled);

// Type-erased version of raw_hash_set::erase_meta_only.
void EraseMetaOnly(CommonFields& c, size_t index, size_t slot_size);

// Function to place in PolicyFunctions::dealloc for raw_hash_sets
// that are using std::allocator. This allows us to share the same
// function body for raw_hash_set instantiations that have the
// same slot alignment.
template <size_t AlignOfSlot>
Y_ABSL_ATTRIBUTE_NOINLINE void DeallocateStandard(CommonFields& common,
                                                const PolicyFunctions& policy) {
  // Unpoison before returning the memory to the allocator.
  SanitizerUnpoisonMemoryRegion(common.slot_array(),
                                policy.slot_size * common.capacity());

  std::allocator<char> alloc;
  common.infoz().Unregister();
  Deallocate<BackingArrayAlignment(AlignOfSlot)>(
      &alloc, common.backing_array_start(),
      common.alloc_size(policy.slot_size, AlignOfSlot));
}

// For trivially relocatable types we use memcpy directly. This allows us to
// share the same function body for raw_hash_set instantiations that have the
// same slot size as long as they are relocatable.
template <size_t SizeOfSlot>
Y_ABSL_ATTRIBUTE_NOINLINE void TransferRelocatable(void*, void* dst, void* src) {
  memcpy(dst, src, SizeOfSlot);
}

// Type erased raw_hash_set::get_hash_ref_fn for the empty hash function case.
const void* GetHashRefForEmptyHasher(const CommonFields& common);

// Given the hash of a value not currently in the table and the first empty
// slot in the probe sequence, finds a viable slot index to insert it at.
//
// In case there's no space left, the table can be resized or rehashed
// (for tables with deleted slots, see FindInsertPositionWithGrowthOrRehash).
//
// In the case of absence of deleted slots and positive growth_left, the element
// can be inserted in the provided `target` position.
//
// When the table has deleted slots (according to GrowthInfo), the target
// position will be searched one more time using `find_first_non_full`.
//
// REQUIRES: Table is not SOO.
// REQUIRES: At least one non-full slot available.
// REQUIRES: `target` is a valid empty position to insert.
size_t PrepareInsertNonSoo(CommonFields& common, size_t hash, FindInfo target,
                           const PolicyFunctions& policy);

// A SwissTable.
//
// Policy: a policy defines how to perform different operations on
// the slots of the hashtable (see hash_policy_traits.h for the full interface
// of policy).
//
// Hash: a (possibly polymorphic) functor that hashes keys of the hashtable. The
// functor should accept a key and return size_t as hash. For best performance
// it is important that the hash function provides high entropy across all bits
// of the hash.
//
// Eq: a (possibly polymorphic) functor that compares two keys for equality. It
// should accept two (of possibly different type) keys and return a bool: true
// if they are equal, false if they are not. If two keys compare equal, then
// their hash values as defined by Hash MUST be equal.
//
// Allocator: an Allocator
// [https://en.cppreference.com/w/cpp/named_req/Allocator] with which
// the storage of the hashtable will be allocated and the elements will be
// constructed and destroyed.
template <class Policy, class Hash, class Eq, class Alloc>
class raw_hash_set {
  using PolicyTraits = hash_policy_traits<Policy>;
  using KeyArgImpl =
      KeyArg<IsTransparent<Eq>::value && IsTransparent<Hash>::value>;

 public:
  using init_type = typename PolicyTraits::init_type;
  using key_type = typename PolicyTraits::key_type;
  // TODO(sbenza): Hide slot_type as it is an implementation detail. Needs user
  // code fixes!
  using slot_type = typename PolicyTraits::slot_type;
  using allocator_type = Alloc;
  using size_type = size_t;
  using difference_type = ptrdiff_t;
  using hasher = Hash;
  using key_equal = Eq;
  using policy_type = Policy;
  using value_type = typename PolicyTraits::value_type;
  using reference = value_type&;
  using const_reference = const value_type&;
  using pointer = typename y_absl::allocator_traits<
      allocator_type>::template rebind_traits<value_type>::pointer;
  using const_pointer = typename y_absl::allocator_traits<
      allocator_type>::template rebind_traits<value_type>::const_pointer;

  // Alias used for heterogeneous lookup functions.
  // `key_arg<K>` evaluates to `K` when the functors are transparent and to
  // `key_type` otherwise. It permits template argument deduction on `K` for the
  // transparent case.
  template <class K>
  using key_arg = typename KeyArgImpl::template type<K, key_type>;

 private:
  // TODO(b/289225379): we could add extra SOO space inside raw_hash_set
  // after CommonFields to allow inlining larger slot_types (e.g. TString),
  // but it's a bit complicated if we want to support incomplete mapped_type in
  // flat_hash_map. We could potentially do this for flat_hash_set and for an
  // allowlist of `mapped_type`s of flat_hash_map that includes e.g. arithmetic
  // types, strings, cords, and pairs/tuples of allowlisted types.
  constexpr static bool SooEnabled() {
    return PolicyTraits::soo_enabled() &&
           sizeof(slot_type) <= sizeof(HeapOrSoo) &&
           alignof(slot_type) <= alignof(HeapOrSoo);
  }

  // Whether `size` fits in the SOO capacity of this table.
  bool fits_in_soo(size_t size) const {
    return SooEnabled() && size <= SooCapacity();
  }
  // Whether this table is in SOO mode or non-SOO mode.
  bool is_soo() const { return fits_in_soo(capacity()); }
  bool is_full_soo() const { return is_soo() && !empty(); }

  // Give an early error when key_type is not hashable/eq.
  auto KeyTypeCanBeHashed(const Hash& h, const key_type& k) -> decltype(h(k));
  auto KeyTypeCanBeEq(const Eq& eq, const key_type& k) -> decltype(eq(k, k));

  using AllocTraits = y_absl::allocator_traits<allocator_type>;
  using SlotAlloc = typename y_absl::allocator_traits<
      allocator_type>::template rebind_alloc<slot_type>;
  // People are often sloppy with the exact type of their allocator (sometimes
  // it has an extra const or is missing the pair, but rebinds made it work
  // anyway).
  using CharAlloc =
      typename y_absl::allocator_traits<Alloc>::template rebind_alloc<char>;
  using SlotAllocTraits = typename y_absl::allocator_traits<
      allocator_type>::template rebind_traits<slot_type>;

  static_assert(std::is_lvalue_reference<reference>::value,
                "Policy::element() must return a reference");

  template <typename T>
  struct SameAsElementReference
      : std::is_same<typename std::remove_cv<
                         typename std::remove_reference<reference>::type>::type,
                     typename std::remove_cv<
                         typename std::remove_reference<T>::type>::type> {};

  // An enabler for insert(T&&): T must be convertible to init_type or be the
  // same as [cv] value_type [ref].
  // Note: we separate SameAsElementReference into its own type to avoid using
  // reference unless we need to. MSVC doesn't seem to like it in some
  // cases.
  template <class T>
  using RequiresInsertable = typename std::enable_if<
      y_absl::disjunction<std::is_convertible<T, init_type>,
                        SameAsElementReference<T>>::value,
      int>::type;

  // RequiresNotInit is a workaround for gcc prior to 7.1.
  // See https://godbolt.org/g/Y4xsUh.
  template <class T>
  using RequiresNotInit =
      typename std::enable_if<!std::is_same<T, init_type>::value, int>::type;

  template <class... Ts>
  using IsDecomposable = IsDecomposable<void, PolicyTraits, Hash, Eq, Ts...>;

 public:
  static_assert(std::is_same<pointer, value_type*>::value,
                "Allocators with custom pointer types are not supported");
  static_assert(std::is_same<const_pointer, const value_type*>::value,
                "Allocators with custom pointer types are not supported");

  class iterator : private HashSetIteratorGenerationInfo {
    friend class raw_hash_set;
    friend struct HashtableFreeFunctionsAccess;

   public:
    using iterator_category = std::forward_iterator_tag;
    using value_type = typename raw_hash_set::value_type;
    using reference =
        y_absl::conditional_t<PolicyTraits::constant_iterators::value,
                            const value_type&, value_type&>;
    using pointer = y_absl::remove_reference_t<reference>*;
    using difference_type = typename raw_hash_set::difference_type;

    iterator() {}

    // PRECONDITION: not an end() iterator.
    reference operator*() const {
      AssertIsFull(ctrl_, generation(), generation_ptr(), "operator*()");
      return unchecked_deref();
    }

    // PRECONDITION: not an end() iterator.
    pointer operator->() const {
      AssertIsFull(ctrl_, generation(), generation_ptr(), "operator->");
      return &operator*();
    }

    // PRECONDITION: not an end() iterator.
    iterator& operator++() {
      AssertIsFull(ctrl_, generation(), generation_ptr(), "operator++");
      ++ctrl_;
      ++slot_;
      skip_empty_or_deleted();
      if (Y_ABSL_PREDICT_FALSE(*ctrl_ == ctrl_t::kSentinel)) ctrl_ = nullptr;
      return *this;
    }
    // PRECONDITION: not an end() iterator.
    iterator operator++(int) {
      auto tmp = *this;
      ++*this;
      return tmp;
    }

    friend bool operator==(const iterator& a, const iterator& b) {
      AssertIsValidForComparison(a.ctrl_, a.generation(), a.generation_ptr());
      AssertIsValidForComparison(b.ctrl_, b.generation(), b.generation_ptr());
      AssertSameContainer(a.ctrl_, b.ctrl_, a.slot_, b.slot_,
                          a.generation_ptr(), b.generation_ptr());
      return a.ctrl_ == b.ctrl_;
    }
    friend bool operator!=(const iterator& a, const iterator& b) {
      return !(a == b);
    }

   private:
    iterator(ctrl_t* ctrl, slot_type* slot,
             const GenerationType* generation_ptr)
        : HashSetIteratorGenerationInfo(generation_ptr),
          ctrl_(ctrl),
          slot_(slot) {
      // This assumption helps the compiler know that any non-end iterator is
      // not equal to any end iterator.
      Y_ABSL_ASSUME(ctrl != nullptr);
    }
    // This constructor is used in begin() to avoid an MSan
    // use-of-uninitialized-value error. Delegating from this constructor to
    // the previous one doesn't avoid the error.
    iterator(ctrl_t* ctrl, MaybeInitializedPtr slot,
             const GenerationType* generation_ptr)
        : HashSetIteratorGenerationInfo(generation_ptr),
          ctrl_(ctrl),
          slot_(to_slot(slot.get())) {
      // This assumption helps the compiler know that any non-end iterator is
      // not equal to any end iterator.
      Y_ABSL_ASSUME(ctrl != nullptr);
    }
    // For end() iterators.
    explicit iterator(const GenerationType* generation_ptr)
        : HashSetIteratorGenerationInfo(generation_ptr), ctrl_(nullptr) {}

    // Fixes up `ctrl_` to point to a full or sentinel by advancing `ctrl_` and
    // `slot_` until they reach one.
    void skip_empty_or_deleted() {
      while (IsEmptyOrDeleted(*ctrl_)) {
        uint32_t shift =
            GroupFullEmptyOrDeleted{ctrl_}.CountLeadingEmptyOrDeleted();
        ctrl_ += shift;
        slot_ += shift;
      }
    }

    ctrl_t* control() const { return ctrl_; }
    slot_type* slot() const { return slot_; }

    // We use EmptyGroup() for default-constructed iterators so that they can
    // be distinguished from end iterators, which have nullptr ctrl_.
    ctrl_t* ctrl_ = EmptyGroup();
    // To avoid uninitialized member warnings, put slot_ in an anonymous union.
    // The member is not initialized on singleton and end iterators.
    union {
      slot_type* slot_;
    };

    // An equality check which skips ABSL Hardening iterator invalidation
    // checks.
    // Should be used when the lifetimes of the iterators are well-enough
    // understood to prove that they cannot be invalid.
    bool unchecked_equals(const iterator& b) { return ctrl_ == b.control(); }

    // Dereferences the iterator without ABSL Hardening iterator invalidation
    // checks.
    reference unchecked_deref() const { return PolicyTraits::element(slot_); }
  };

  class const_iterator {
    friend class raw_hash_set;
    template <class Container, typename Enabler>
    friend struct y_absl::container_internal::hashtable_debug_internal::
        HashtableDebugAccess;

   public:
    using iterator_category = typename iterator::iterator_category;
    using value_type = typename raw_hash_set::value_type;
    using reference = typename raw_hash_set::const_reference;
    using pointer = typename raw_hash_set::const_pointer;
    using difference_type = typename raw_hash_set::difference_type;

    const_iterator() = default;
    // Implicit construction from iterator.
    const_iterator(iterator i) : inner_(std::move(i)) {}  // NOLINT

    reference operator*() const { return *inner_; }
    pointer operator->() const { return inner_.operator->(); }

    const_iterator& operator++() {
      ++inner_;
      return *this;
    }
    const_iterator operator++(int) { return inner_++; }

    friend bool operator==(const const_iterator& a, const const_iterator& b) {
      return a.inner_ == b.inner_;
    }
    friend bool operator!=(const const_iterator& a, const const_iterator& b) {
      return !(a == b);
    }

   private:
    const_iterator(const ctrl_t* ctrl, const slot_type* slot,
                   const GenerationType* gen)
        : inner_(const_cast<ctrl_t*>(ctrl), const_cast<slot_type*>(slot), gen) {
    }
    ctrl_t* control() const { return inner_.control(); }
    slot_type* slot() const { return inner_.slot(); }

    iterator inner_;

    bool unchecked_equals(const const_iterator& b) {
      return inner_.unchecked_equals(b.inner_);
    }
  };

  using node_type = node_handle<Policy, hash_policy_traits<Policy>, Alloc>;
  using insert_return_type = InsertReturnType<iterator, node_type>;

  // Note: can't use `= default` due to non-default noexcept (causes
  // problems for some compilers). NOLINTNEXTLINE
  raw_hash_set() noexcept(
      std::is_nothrow_default_constructible<hasher>::value &&
      std::is_nothrow_default_constructible<key_equal>::value &&
      std::is_nothrow_default_constructible<allocator_type>::value) {}

  Y_ABSL_ATTRIBUTE_NOINLINE explicit raw_hash_set(
      size_t bucket_count, const hasher& hash = hasher(),
      const key_equal& eq = key_equal(),
      const allocator_type& alloc = allocator_type())
      : settings_(CommonFields::CreateDefault<SooEnabled()>(), hash, eq,
                  alloc) {
    if (bucket_count > (SooEnabled() ? SooCapacity() : 0)) {
      resize(NormalizeCapacity(bucket_count));
    }
  }

  raw_hash_set(size_t bucket_count, const hasher& hash,
               const allocator_type& alloc)
      : raw_hash_set(bucket_count, hash, key_equal(), alloc) {}

  raw_hash_set(size_t bucket_count, const allocator_type& alloc)
      : raw_hash_set(bucket_count, hasher(), key_equal(), alloc) {}

  explicit raw_hash_set(const allocator_type& alloc)
      : raw_hash_set(0, hasher(), key_equal(), alloc) {}

  template <class InputIter>
  raw_hash_set(InputIter first, InputIter last, size_t bucket_count = 0,
               const hasher& hash = hasher(), const key_equal& eq = key_equal(),
               const allocator_type& alloc = allocator_type())
      : raw_hash_set(SelectBucketCountForIterRange(first, last, bucket_count),
                     hash, eq, alloc) {
    insert(first, last);
  }

  template <class InputIter>
  raw_hash_set(InputIter first, InputIter last, size_t bucket_count,
               const hasher& hash, const allocator_type& alloc)
      : raw_hash_set(first, last, bucket_count, hash, key_equal(), alloc) {}

  template <class InputIter>
  raw_hash_set(InputIter first, InputIter last, size_t bucket_count,
               const allocator_type& alloc)
      : raw_hash_set(first, last, bucket_count, hasher(), key_equal(), alloc) {}

  template <class InputIter>
  raw_hash_set(InputIter first, InputIter last, const allocator_type& alloc)
      : raw_hash_set(first, last, 0, hasher(), key_equal(), alloc) {}

  // Instead of accepting std::initializer_list<value_type> as the first
  // argument like std::unordered_set<value_type> does, we have two overloads
  // that accept std::initializer_list<T> and std::initializer_list<init_type>.
  // This is advantageous for performance.
  //
  //   // Turns {"abc", "def"} into std::initializer_list<TString>, then
  //   // copies the strings into the set.
  //   std::unordered_set<TString> s = {"abc", "def"};
  //
  //   // Turns {"abc", "def"} into std::initializer_list<const char*>, then
  //   // copies the strings into the set.
  //   y_absl::flat_hash_set<TString> s = {"abc", "def"};
  //
  // The same trick is used in insert().
  //
  // The enabler is necessary to prevent this constructor from triggering where
  // the copy constructor is meant to be called.
  //
  //   y_absl::flat_hash_set<int> a, b{a};
  //
  // RequiresNotInit<T> is a workaround for gcc prior to 7.1.
  template <class T, RequiresNotInit<T> = 0, RequiresInsertable<T> = 0>
  raw_hash_set(std::initializer_list<T> init, size_t bucket_count = 0,
               const hasher& hash = hasher(), const key_equal& eq = key_equal(),
               const allocator_type& alloc = allocator_type())
      : raw_hash_set(init.begin(), init.end(), bucket_count, hash, eq, alloc) {}

  raw_hash_set(std::initializer_list<init_type> init, size_t bucket_count = 0,
               const hasher& hash = hasher(), const key_equal& eq = key_equal(),
               const allocator_type& alloc = allocator_type())
      : raw_hash_set(init.begin(), init.end(), bucket_count, hash, eq, alloc) {}

  template <class T, RequiresNotInit<T> = 0, RequiresInsertable<T> = 0>
  raw_hash_set(std::initializer_list<T> init, size_t bucket_count,
               const hasher& hash, const allocator_type& alloc)
      : raw_hash_set(init, bucket_count, hash, key_equal(), alloc) {}

  raw_hash_set(std::initializer_list<init_type> init, size_t bucket_count,
               const hasher& hash, const allocator_type& alloc)
      : raw_hash_set(init, bucket_count, hash, key_equal(), alloc) {}

  template <class T, RequiresNotInit<T> = 0, RequiresInsertable<T> = 0>
  raw_hash_set(std::initializer_list<T> init, size_t bucket_count,
               const allocator_type& alloc)
      : raw_hash_set(init, bucket_count, hasher(), key_equal(), alloc) {}

  raw_hash_set(std::initializer_list<init_type> init, size_t bucket_count,
               const allocator_type& alloc)
      : raw_hash_set(init, bucket_count, hasher(), key_equal(), alloc) {}

  template <class T, RequiresNotInit<T> = 0, RequiresInsertable<T> = 0>
  raw_hash_set(std::initializer_list<T> init, const allocator_type& alloc)
      : raw_hash_set(init, 0, hasher(), key_equal(), alloc) {}

  raw_hash_set(std::initializer_list<init_type> init,
               const allocator_type& alloc)
      : raw_hash_set(init, 0, hasher(), key_equal(), alloc) {}

  raw_hash_set(const raw_hash_set& that)
      : raw_hash_set(that, AllocTraits::select_on_container_copy_construction(
                               that.alloc_ref())) {}

  raw_hash_set(const raw_hash_set& that, const allocator_type& a)
      : raw_hash_set(GrowthToLowerboundCapacity(that.size()), that.hash_ref(),
                     that.eq_ref(), a) {
    const size_t size = that.size();
    if (size == 0) {
      return;
    }
    // We don't use `that.is_soo()` here because `that` can have non-SOO
    // capacity but have a size that fits into SOO capacity.
    if (fits_in_soo(size)) {
      assert(size == 1);
      common().set_full_soo();
      emplace_at(soo_iterator(), *that.begin());
      const HashtablezInfoHandle infoz = try_sample_soo();
      if (infoz.IsSampled()) resize_with_soo_infoz(infoz);
      return;
    }
    assert(!that.is_soo());
    const size_t cap = capacity();
    // Note about single group tables:
    // 1. It is correct to have any order of elements.
    // 2. Order has to be non deterministic.
    // 3. We are assigning elements with arbitrary `shift` starting from
    //    `capacity + shift` position.
    // 4. `shift` must be coprime with `capacity + 1` in order to be able to use
    //     modular arithmetic to traverse all positions, instead if cycling
    //     through a subset of positions. Odd numbers are coprime with any
    //     `capacity + 1` (2^N).
    size_t offset = cap;
    const size_t shift =
        is_single_group(cap) ? (PerTableSalt(control()) | 1) : 0;
    IterateOverFullSlots(
        that.common(), that.slot_array(),
        [&](const ctrl_t* that_ctrl,
            slot_type* that_slot) Y_ABSL_ATTRIBUTE_ALWAYS_INLINE {
          if (shift == 0) {
            // Big tables case. Position must be searched via probing.
            // The table is guaranteed to be empty, so we can do faster than
            // a full `insert`.
            const size_t hash = PolicyTraits::apply(
                HashElement{hash_ref()}, PolicyTraits::element(that_slot));
            FindInfo target = find_first_non_full_outofline(common(), hash);
            infoz().RecordInsert(hash, target.probe_length);
            offset = target.offset;
          } else {
            // Small tables case. Next position is computed via shift.
            offset = (offset + shift) & cap;
          }
          const h2_t h2 = static_cast<h2_t>(*that_ctrl);
          assert(  // We rely that hash is not changed for small tables.
              H2(PolicyTraits::apply(HashElement{hash_ref()},
                                     PolicyTraits::element(that_slot))) == h2 &&
              "hash function value changed unexpectedly during the copy");
          SetCtrl(common(), offset, h2, sizeof(slot_type));
          emplace_at(iterator_at(offset), PolicyTraits::element(that_slot));
          common().maybe_increment_generation_on_insert();
        });
    if (shift != 0) {
      // On small table copy we do not record individual inserts.
      // RecordInsert requires hash, but it is unknown for small tables.
      infoz().RecordStorageChanged(size, cap);
    }
    common().set_size(size);
    growth_info().OverwriteManyEmptyAsFull(size);
  }

  Y_ABSL_ATTRIBUTE_NOINLINE raw_hash_set(raw_hash_set&& that) noexcept(
      std::is_nothrow_copy_constructible<hasher>::value &&
      std::is_nothrow_copy_constructible<key_equal>::value &&
      std::is_nothrow_copy_constructible<allocator_type>::value)
      :  // Hash, equality and allocator are copied instead of moved because
         // `that` must be left valid. If Hash is std::function<Key>, moving it
         // would create a nullptr functor that cannot be called.
         // TODO(b/296061262): move instead of copying hash/eq/alloc.
         // Note: we avoid using exchange for better generated code.
        settings_(PolicyTraits::transfer_uses_memcpy() || !that.is_full_soo()
                      ? std::move(that.common())
                      : CommonFields{full_soo_tag_t{}},
                  that.hash_ref(), that.eq_ref(), that.alloc_ref()) {
    if (!PolicyTraits::transfer_uses_memcpy() && that.is_full_soo()) {
      transfer(soo_slot(), that.soo_slot());
    }
    that.common() = CommonFields::CreateDefault<SooEnabled()>();
    maybe_increment_generation_or_rehash_on_move();
  }

  raw_hash_set(raw_hash_set&& that, const allocator_type& a)
      : settings_(CommonFields::CreateDefault<SooEnabled()>(), that.hash_ref(),
                  that.eq_ref(), a) {
    if (a == that.alloc_ref()) {
      swap_common(that);
      maybe_increment_generation_or_rehash_on_move();
    } else {
      move_elements_allocs_unequal(std::move(that));
    }
  }

  raw_hash_set& operator=(const raw_hash_set& that) {
    if (Y_ABSL_PREDICT_FALSE(this == &that)) return *this;
    constexpr bool propagate_alloc =
        AllocTraits::propagate_on_container_copy_assignment::value;
    // TODO(ezb): maybe avoid allocating a new backing array if this->capacity()
    // is an exact match for that.size(). If this->capacity() is too big, then
    // it would make iteration very slow to reuse the allocation. Maybe we can
    // do the same heuristic as clear() and reuse if it's small enough.
    raw_hash_set tmp(that, propagate_alloc ? that.alloc_ref() : alloc_ref());
    // NOLINTNEXTLINE: not returning *this for performance.
    return assign_impl<propagate_alloc>(std::move(tmp));
  }

  raw_hash_set& operator=(raw_hash_set&& that) noexcept(
      y_absl::allocator_traits<allocator_type>::is_always_equal::value &&
      std::is_nothrow_move_assignable<hasher>::value &&
      std::is_nothrow_move_assignable<key_equal>::value) {
    // TODO(sbenza): We should only use the operations from the noexcept clause
    // to make sure we actually adhere to that contract.
    // NOLINTNEXTLINE: not returning *this for performance.
    return move_assign(
        std::move(that),
        typename AllocTraits::propagate_on_container_move_assignment());
  }

  ~raw_hash_set() { destructor_impl(); }

  iterator begin() Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    if (Y_ABSL_PREDICT_FALSE(empty())) return end();
    if (is_soo()) return soo_iterator();
    iterator it = {control(), common().slots_union(),
                   common().generation_ptr()};
    it.skip_empty_or_deleted();
    assert(IsFull(*it.control()));
    return it;
  }
  iterator end() Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return iterator(common().generation_ptr());
  }

  const_iterator begin() const Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return const_cast<raw_hash_set*>(this)->begin();
  }
  const_iterator end() const Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return iterator(common().generation_ptr());
  }
  const_iterator cbegin() const Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return begin();
  }
  const_iterator cend() const Y_ABSL_ATTRIBUTE_LIFETIME_BOUND { return end(); }

  bool empty() const { return !size(); }
  size_t size() const { return common().size(); }
  size_t capacity() const {
    const size_t cap = common().capacity();
    // Compiler complains when using functions in assume so use local variables.
    Y_ABSL_ATTRIBUTE_UNUSED static constexpr bool kEnabled = SooEnabled();
    Y_ABSL_ATTRIBUTE_UNUSED static constexpr size_t kCapacity = SooCapacity();
    Y_ABSL_ASSUME(!kEnabled || cap >= kCapacity);
    return cap;
  }
  size_t max_size() const { return (std::numeric_limits<size_t>::max)(); }

  Y_ABSL_ATTRIBUTE_REINITIALIZES void clear() {
    // Iterating over this container is O(bucket_count()). When bucket_count()
    // is much greater than size(), iteration becomes prohibitively expensive.
    // For clear() it is more important to reuse the allocated array when the
    // container is small because allocation takes comparatively long time
    // compared to destruction of the elements of the container. So we pick the
    // largest bucket_count() threshold for which iteration is still fast and
    // past that we simply deallocate the array.
    const size_t cap = capacity();
    if (cap == 0) {
      // Already guaranteed to be empty; so nothing to do.
    } else if (is_soo()) {
      if (!empty()) destroy(soo_slot());
      common().set_empty_soo();
    } else {
      destroy_slots();
      ClearBackingArray(common(), GetPolicyFunctions(), /*reuse=*/cap < 128,
                        SooEnabled());
    }
    common().set_reserved_growth(0);
    common().set_reservation_size(0);
  }

  // This overload kicks in when the argument is an rvalue of insertable and
  // decomposable type other than init_type.
  //
  //   flat_hash_map<TString, int> m;
  //   m.insert(std::make_pair("abc", 42));
  // TODO(cheshire): A type alias T2 is introduced as a workaround for the nvcc
  // bug.
  template <class T, RequiresInsertable<T> = 0, class T2 = T,
            typename std::enable_if<IsDecomposable<T2>::value, int>::type = 0,
            T* = nullptr>
  std::pair<iterator, bool> insert(T&& value) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return emplace(std::forward<T>(value));
  }

  // This overload kicks in when the argument is a bitfield or an lvalue of
  // insertable and decomposable type.
  //
  //   union { int n : 1; };
  //   flat_hash_set<int> s;
  //   s.insert(n);
  //
  //   flat_hash_set<TString> s;
  //   const char* p = "hello";
  //   s.insert(p);
  //
  template <
      class T, RequiresInsertable<const T&> = 0,
      typename std::enable_if<IsDecomposable<const T&>::value, int>::type = 0>
  std::pair<iterator, bool> insert(const T& value)
      Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return emplace(value);
  }

  // This overload kicks in when the argument is an rvalue of init_type. Its
  // purpose is to handle brace-init-list arguments.
  //
  //   flat_hash_map<TString, int> s;
  //   s.insert({"abc", 42});
  std::pair<iterator, bool> insert(init_type&& value)
      Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return emplace(std::move(value));
  }

  // TODO(cheshire): A type alias T2 is introduced as a workaround for the nvcc
  // bug.
  template <class T, RequiresInsertable<T> = 0, class T2 = T,
            typename std::enable_if<IsDecomposable<T2>::value, int>::type = 0,
            T* = nullptr>
  iterator insert(const_iterator, T&& value) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return insert(std::forward<T>(value)).first;
  }

  template <
      class T, RequiresInsertable<const T&> = 0,
      typename std::enable_if<IsDecomposable<const T&>::value, int>::type = 0>
  iterator insert(const_iterator,
                  const T& value) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return insert(value).first;
  }

  iterator insert(const_iterator,
                  init_type&& value) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return insert(std::move(value)).first;
  }

  template <class InputIt>
  void insert(InputIt first, InputIt last) {
    for (; first != last; ++first) emplace(*first);
  }

  template <class T, RequiresNotInit<T> = 0, RequiresInsertable<const T&> = 0>
  void insert(std::initializer_list<T> ilist) {
    insert(ilist.begin(), ilist.end());
  }

  void insert(std::initializer_list<init_type> ilist) {
    insert(ilist.begin(), ilist.end());
  }

  insert_return_type insert(node_type&& node) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    if (!node) return {end(), false, node_type()};
    const auto& elem = PolicyTraits::element(CommonAccess::GetSlot(node));
    auto res = PolicyTraits::apply(
        InsertSlot<false>{*this, std::move(*CommonAccess::GetSlot(node))},
        elem);
    if (res.second) {
      CommonAccess::Reset(&node);
      return {res.first, true, node_type()};
    } else {
      return {res.first, false, std::move(node)};
    }
  }

  iterator insert(const_iterator,
                  node_type&& node) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    auto res = insert(std::move(node));
    node = std::move(res.node);
    return res.position;
  }

  // This overload kicks in if we can deduce the key from args. This enables us
  // to avoid constructing value_type if an entry with the same key already
  // exists.
  //
  // For example:
  //
  //   flat_hash_map<TString, TString> m = {{"abc", "def"}};
  //   // Creates no TString copies and makes no heap allocations.
  //   m.emplace("abc", "xyz");
  template <class... Args, typename std::enable_if<
                               IsDecomposable<Args...>::value, int>::type = 0>
  std::pair<iterator, bool> emplace(Args&&... args)
      Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return PolicyTraits::apply(EmplaceDecomposable{*this},
                               std::forward<Args>(args)...);
  }

  // This overload kicks in if we cannot deduce the key from args. It constructs
  // value_type unconditionally and then either moves it into the table or
  // destroys.
  template <class... Args, typename std::enable_if<
                               !IsDecomposable<Args...>::value, int>::type = 0>
  std::pair<iterator, bool> emplace(Args&&... args)
      Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    alignas(slot_type) unsigned char raw[sizeof(slot_type)];
    slot_type* slot = to_slot(&raw);

    construct(slot, std::forward<Args>(args)...);
    const auto& elem = PolicyTraits::element(slot);
    return PolicyTraits::apply(InsertSlot<true>{*this, std::move(*slot)}, elem);
  }

  template <class... Args>
  iterator emplace_hint(const_iterator,
                        Args&&... args) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return emplace(std::forward<Args>(args)...).first;
  }

  // Extension API: support for lazy emplace.
  //
  // Looks up key in the table. If found, returns the iterator to the element.
  // Otherwise calls `f` with one argument of type `raw_hash_set::constructor`,
  // and returns an iterator to the new element.
  //
  // `f` must abide by several restrictions:
  //  - it MUST call `raw_hash_set::constructor` with arguments as if a
  //    `raw_hash_set::value_type` is constructed,
  //  - it MUST NOT access the container before the call to
  //    `raw_hash_set::constructor`, and
  //  - it MUST NOT erase the lazily emplaced element.
  // Doing any of these is undefined behavior.
  //
  // For example:
  //
  //   std::unordered_set<ArenaString> s;
  //   // Makes ArenaStr even if "abc" is in the map.
  //   s.insert(ArenaString(&arena, "abc"));
  //
  //   flat_hash_set<ArenaStr> s;
  //   // Makes ArenaStr only if "abc" is not in the map.
  //   s.lazy_emplace("abc", [&](const constructor& ctor) {
  //     ctor(&arena, "abc");
  //   });
  //
  // WARNING: This API is currently experimental. If there is a way to implement
  // the same thing with the rest of the API, prefer that.
  class constructor {
    friend class raw_hash_set;

   public:
    template <class... Args>
    void operator()(Args&&... args) const {
      assert(*slot_);
      PolicyTraits::construct(alloc_, *slot_, std::forward<Args>(args)...);
      *slot_ = nullptr;
    }

   private:
    constructor(allocator_type* a, slot_type** slot) : alloc_(a), slot_(slot) {}

    allocator_type* alloc_;
    slot_type** slot_;
  };

  template <class K = key_type, class F>
  iterator lazy_emplace(const key_arg<K>& key,
                        F&& f) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    auto res = find_or_prepare_insert(key);
    if (res.second) {
      slot_type* slot = res.first.slot();
      std::forward<F>(f)(constructor(&alloc_ref(), &slot));
      assert(!slot);
    }
    return res.first;
  }

  // Extension API: support for heterogeneous keys.
  //
  //   std::unordered_set<TString> s;
  //   // Turns "abc" into TString.
  //   s.erase("abc");
  //
  //   flat_hash_set<TString> s;
  //   // Uses "abc" directly without copying it into TString.
  //   s.erase("abc");
  template <class K = key_type>
  size_type erase(const key_arg<K>& key) {
    auto it = find(key);
    if (it == end()) return 0;
    erase(it);
    return 1;
  }

  // Erases the element pointed to by `it`.  Unlike `std::unordered_set::erase`,
  // this method returns void to reduce algorithmic complexity to O(1).  The
  // iterator is invalidated, so any increment should be done before calling
  // erase.  In order to erase while iterating across a map, use the following
  // idiom (which also works for some standard containers):
  //
  // for (auto it = m.begin(), end = m.end(); it != end;) {
  //   // `erase()` will invalidate `it`, so advance `it` first.
  //   auto copy_it = it++;
  //   if (<pred>) {
  //     m.erase(copy_it);
  //   }
  // }
  void erase(const_iterator cit) { erase(cit.inner_); }

  // This overload is necessary because otherwise erase<K>(const K&) would be
  // a better match if non-const iterator is passed as an argument.
  void erase(iterator it) {
    AssertIsFull(it.control(), it.generation(), it.generation_ptr(), "erase()");
    destroy(it.slot());
    if (is_soo()) {
      common().set_empty_soo();
    } else {
      erase_meta_only(it);
    }
  }

  iterator erase(const_iterator first,
                 const_iterator last) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    // We check for empty first because ClearBackingArray requires that
    // capacity() > 0 as a precondition.
    if (empty()) return end();
    if (first == last) return last.inner_;
    if (is_soo()) {
      destroy(soo_slot());
      common().set_empty_soo();
      return end();
    }
    if (first == begin() && last == end()) {
      // TODO(ezb): we access control bytes in destroy_slots so it could make
      // sense to combine destroy_slots and ClearBackingArray to avoid cache
      // misses when the table is large. Note that we also do this in clear().
      destroy_slots();
      ClearBackingArray(common(), GetPolicyFunctions(), /*reuse=*/true,
                        SooEnabled());
      common().set_reserved_growth(common().reservation_size());
      return end();
    }
    while (first != last) {
      erase(first++);
    }
    return last.inner_;
  }

  // Moves elements from `src` into `this`.
  // If the element already exists in `this`, it is left unmodified in `src`.
  template <typename H, typename E>
  void merge(raw_hash_set<Policy, H, E, Alloc>& src) {  // NOLINT
    assert(this != &src);
    // Returns whether insertion took place.
    const auto insert_slot = [this](slot_type* src_slot) {
      return PolicyTraits::apply(InsertSlot<false>{*this, std::move(*src_slot)},
                                 PolicyTraits::element(src_slot))
          .second;
    };

    if (src.is_soo()) {
      if (src.empty()) return;
      if (insert_slot(src.soo_slot())) src.common().set_empty_soo();
      return;
    }
    for (auto it = src.begin(), e = src.end(); it != e;) {
      auto next = std::next(it);
      if (insert_slot(it.slot())) src.erase_meta_only(it);
      it = next;
    }
  }

  template <typename H, typename E>
  void merge(raw_hash_set<Policy, H, E, Alloc>&& src) {
    merge(src);
  }

  node_type extract(const_iterator position) {
    AssertIsFull(position.control(), position.inner_.generation(),
                 position.inner_.generation_ptr(), "extract()");
    auto node = CommonAccess::Transfer<node_type>(alloc_ref(), position.slot());
    if (is_soo()) {
      common().set_empty_soo();
    } else {
      erase_meta_only(position);
    }
    return node;
  }

  template <
      class K = key_type,
      typename std::enable_if<!std::is_same<K, iterator>::value, int>::type = 0>
  node_type extract(const key_arg<K>& key) {
    auto it = find(key);
    return it == end() ? node_type() : extract(const_iterator{it});
  }

  void swap(raw_hash_set& that) noexcept(
      IsNoThrowSwappable<hasher>() && IsNoThrowSwappable<key_equal>() &&
      IsNoThrowSwappable<allocator_type>(
          typename AllocTraits::propagate_on_container_swap{})) {
    using std::swap;
    swap_common(that);
    swap(hash_ref(), that.hash_ref());
    swap(eq_ref(), that.eq_ref());
    SwapAlloc(alloc_ref(), that.alloc_ref(),
              typename AllocTraits::propagate_on_container_swap{});
  }

  void rehash(size_t n) {
    const size_t cap = capacity();
    if (n == 0) {
      if (cap == 0 || is_soo()) return;
      if (empty()) {
        ClearBackingArray(common(), GetPolicyFunctions(), /*reuse=*/false,
                          SooEnabled());
        return;
      }
      if (fits_in_soo(size())) {
        // When the table is already sampled, we keep it sampled.
        if (infoz().IsSampled()) {
          const size_t kInitialSampledCapacity = NextCapacity(SooCapacity());
          if (capacity() > kInitialSampledCapacity) {
            resize(kInitialSampledCapacity);
          }
          // This asserts that we didn't lose sampling coverage in `resize`.
          assert(infoz().IsSampled());
          return;
        }
        alignas(slot_type) unsigned char slot_space[sizeof(slot_type)];
        slot_type* tmp_slot = to_slot(slot_space);
        transfer(tmp_slot, begin().slot());
        ClearBackingArray(common(), GetPolicyFunctions(), /*reuse=*/false,
                          SooEnabled());
        transfer(soo_slot(), tmp_slot);
        common().set_full_soo();
        return;
      }
    }

    // bitor is a faster way of doing `max` here. We will round up to the next
    // power-of-2-minus-1, so bitor is good enough.
    auto m = NormalizeCapacity(n | GrowthToLowerboundCapacity(size()));
    // n == 0 unconditionally rehashes as per the standard.
    if (n == 0 || m > cap) {
      resize(m);

      // This is after resize, to ensure that we have completed the allocation
      // and have potentially sampled the hashtable.
      infoz().RecordReservation(n);
    }
  }

  void reserve(size_t n) {
    const size_t max_size_before_growth =
        is_soo() ? SooCapacity() : size() + growth_left();
    if (n > max_size_before_growth) {
      size_t m = GrowthToLowerboundCapacity(n);
      resize(NormalizeCapacity(m));

      // This is after resize, to ensure that we have completed the allocation
      // and have potentially sampled the hashtable.
      infoz().RecordReservation(n);
    }
    common().reset_reserved_growth(n);
    common().set_reservation_size(n);
  }

  // Extension API: support for heterogeneous keys.
  //
  //   std::unordered_set<TString> s;
  //   // Turns "abc" into TString.
  //   s.count("abc");
  //
  //   ch_set<TString> s;
  //   // Uses "abc" directly without copying it into TString.
  //   s.count("abc");
  template <class K = key_type>
  size_t count(const key_arg<K>& key) const {
    return find(key) == end() ? 0 : 1;
  }

  // Issues CPU prefetch instructions for the memory needed to find or insert
  // a key.  Like all lookup functions, this support heterogeneous keys.
  //
  // NOTE: This is a very low level operation and should not be used without
  // specific benchmarks indicating its importance.
  template <class K = key_type>
  void prefetch(const key_arg<K>& key) const {
    if (SooEnabled() ? is_soo() : capacity() == 0) return;
    (void)key;
    // Avoid probing if we won't be able to prefetch the addresses received.
#ifdef Y_ABSL_HAVE_PREFETCH
    prefetch_heap_block();
    auto seq = probe(common(), hash_ref()(key));
    PrefetchToLocalCache(control() + seq.offset());
    PrefetchToLocalCache(slot_array() + seq.offset());
#endif  // Y_ABSL_HAVE_PREFETCH
  }

  // The API of find() has two extensions.
  //
  // 1. The hash can be passed by the user. It must be equal to the hash of the
  // key.
  //
  // 2. The type of the key argument doesn't have to be key_type. This is so
  // called heterogeneous key support.
  template <class K = key_type>
  iterator find(const key_arg<K>& key,
                size_t hash) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    AssertHashEqConsistent(key);
    if (is_soo()) return find_soo(key);
    return find_non_soo(key, hash);
  }
  template <class K = key_type>
  iterator find(const key_arg<K>& key) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    AssertHashEqConsistent(key);
    if (is_soo()) return find_soo(key);
    prefetch_heap_block();
    return find_non_soo(key, hash_ref()(key));
  }

  template <class K = key_type>
  const_iterator find(const key_arg<K>& key,
                      size_t hash) const Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return const_cast<raw_hash_set*>(this)->find(key, hash);
  }
  template <class K = key_type>
  const_iterator find(const key_arg<K>& key) const
      Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return const_cast<raw_hash_set*>(this)->find(key);
  }

  template <class K = key_type>
  bool contains(const key_arg<K>& key) const {
    // Here neither the iterator returned by `find()` nor `end()` can be invalid
    // outside of potential thread-safety issues.
    // `find()`'s return value is constructed, used, and then destructed
    // all in this context.
    return !find(key).unchecked_equals(end());
  }

  template <class K = key_type>
  std::pair<iterator, iterator> equal_range(const key_arg<K>& key)
      Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    auto it = find(key);
    if (it != end()) return {it, std::next(it)};
    return {it, it};
  }
  template <class K = key_type>
  std::pair<const_iterator, const_iterator> equal_range(
      const key_arg<K>& key) const Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    auto it = find(key);
    if (it != end()) return {it, std::next(it)};
    return {it, it};
  }

  size_t bucket_count() const { return capacity(); }
  float load_factor() const {
    return capacity() ? static_cast<double>(size()) / capacity() : 0.0;
  }
  float max_load_factor() const { return 1.0f; }
  void max_load_factor(float) {
    // Does nothing.
  }

  hasher hash_function() const { return hash_ref(); }
  key_equal key_eq() const { return eq_ref(); }
  allocator_type get_allocator() const { return alloc_ref(); }

  friend bool operator==(const raw_hash_set& a, const raw_hash_set& b) {
    if (a.size() != b.size()) return false;
    const raw_hash_set* outer = &a;
    const raw_hash_set* inner = &b;
    if (outer->capacity() > inner->capacity()) std::swap(outer, inner);
    for (const value_type& elem : *outer) {
      auto it = PolicyTraits::apply(FindElement{*inner}, elem);
      if (it == inner->end() || !(*it == elem)) return false;
    }
    return true;
  }

  friend bool operator!=(const raw_hash_set& a, const raw_hash_set& b) {
    return !(a == b);
  }

  template <typename H>
  friend typename std::enable_if<H::template is_hashable<value_type>::value,
                                 H>::type
  AbslHashValue(H h, const raw_hash_set& s) {
    return H::combine(H::combine_unordered(std::move(h), s.begin(), s.end()),
                      s.size());
  }

  friend void swap(raw_hash_set& a,
                   raw_hash_set& b) noexcept(noexcept(a.swap(b))) {
    a.swap(b);
  }

 private:
  template <class Container, typename Enabler>
  friend struct y_absl::container_internal::hashtable_debug_internal::
      HashtableDebugAccess;

  friend struct y_absl::container_internal::HashtableFreeFunctionsAccess;

  struct FindElement {
    template <class K, class... Args>
    const_iterator operator()(const K& key, Args&&...) const {
      return s.find(key);
    }
    const raw_hash_set& s;
  };

  struct HashElement {
    template <class K, class... Args>
    size_t operator()(const K& key, Args&&...) const {
      return h(key);
    }
    const hasher& h;
  };

  template <class K1>
  struct EqualElement {
    template <class K2, class... Args>
    bool operator()(const K2& lhs, Args&&...) const {
      return eq(lhs, rhs);
    }
    const K1& rhs;
    const key_equal& eq;
  };

  struct EmplaceDecomposable {
    template <class K, class... Args>
    std::pair<iterator, bool> operator()(const K& key, Args&&... args) const {
      auto res = s.find_or_prepare_insert(key);
      if (res.second) {
        s.emplace_at(res.first, std::forward<Args>(args)...);
      }
      return res;
    }
    raw_hash_set& s;
  };

  template <bool do_destroy>
  struct InsertSlot {
    template <class K, class... Args>
    std::pair<iterator, bool> operator()(const K& key, Args&&...) && {
      auto res = s.find_or_prepare_insert(key);
      if (res.second) {
        s.transfer(res.first.slot(), &slot);
      } else if (do_destroy) {
        s.destroy(&slot);
      }
      return res;
    }
    raw_hash_set& s;
    // Constructed slot. Either moved into place or destroyed.
    slot_type&& slot;
  };

  // TODO(b/303305702): re-enable reentrant validation.
  template <typename... Args>
  inline void construct(slot_type* slot, Args&&... args) {
    PolicyTraits::construct(&alloc_ref(), slot, std::forward<Args>(args)...);
  }
  inline void destroy(slot_type* slot) {
    PolicyTraits::destroy(&alloc_ref(), slot);
  }
  inline void transfer(slot_type* to, slot_type* from) {
    PolicyTraits::transfer(&alloc_ref(), to, from);
  }

  // TODO(b/289225379): consider having a helper class that has the impls for
  // SOO functionality.
  template <class K = key_type>
  iterator find_soo(const key_arg<K>& key) {
    assert(is_soo());
    return empty() || !PolicyTraits::apply(EqualElement<K>{key, eq_ref()},
                                           PolicyTraits::element(soo_slot()))
               ? end()
               : soo_iterator();
  }

  template <class K = key_type>
  iterator find_non_soo(const key_arg<K>& key, size_t hash) {
    assert(!is_soo());
    auto seq = probe(common(), hash);
    const ctrl_t* ctrl = control();
    while (true) {
      Group g{ctrl + seq.offset()};
      for (uint32_t i : g.Match(H2(hash))) {
        if (Y_ABSL_PREDICT_TRUE(PolicyTraits::apply(
                EqualElement<K>{key, eq_ref()},
                PolicyTraits::element(slot_array() + seq.offset(i)))))
          return iterator_at(seq.offset(i));
      }
      if (Y_ABSL_PREDICT_TRUE(g.MaskEmpty())) return end();
      seq.next();
      assert(seq.index() <= capacity() && "full table!");
    }
  }

  // Conditionally samples hashtablez for SOO tables. This should be called on
  // insertion into an empty SOO table and in copy construction when the size
  // can fit in SOO capacity.
  inline HashtablezInfoHandle try_sample_soo() {
    assert(is_soo());
    if (!ShouldSampleHashtablezInfo<CharAlloc>()) return HashtablezInfoHandle{};
    return Sample(sizeof(slot_type), sizeof(key_type), sizeof(value_type),
                  SooCapacity());
  }

  inline void destroy_slots() {
    assert(!is_soo());
    if (PolicyTraits::template destroy_is_trivial<Alloc>()) return;
    IterateOverFullSlots(
        common(), slot_array(),
        [&](const ctrl_t*, slot_type* slot)
            Y_ABSL_ATTRIBUTE_ALWAYS_INLINE { this->destroy(slot); });
  }

  inline void dealloc() {
    assert(capacity() != 0);
    // Unpoison before returning the memory to the allocator.
    SanitizerUnpoisonMemoryRegion(slot_array(), sizeof(slot_type) * capacity());
    infoz().Unregister();
    Deallocate<BackingArrayAlignment(alignof(slot_type))>(
        &alloc_ref(), common().backing_array_start(),
        common().alloc_size(sizeof(slot_type), alignof(slot_type)));
  }

  inline void destructor_impl() {
    if (capacity() == 0) return;
    if (is_soo()) {
      if (!empty()) {
        Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(destroy(soo_slot()));
      }
      return;
    }
    destroy_slots();
    dealloc();
  }

  // Erases, but does not destroy, the value pointed to by `it`.
  //
  // This merely updates the pertinent control byte. This can be used in
  // conjunction with Policy::transfer to move the object to another place.
  void erase_meta_only(const_iterator it) {
    assert(!is_soo());
    EraseMetaOnly(common(), static_cast<size_t>(it.control() - control()),
                  sizeof(slot_type));
  }

  size_t hash_of(slot_type* slot) const {
    return PolicyTraits::apply(HashElement{hash_ref()},
                               PolicyTraits::element(slot));
  }

  // Resizes table to the new capacity and move all elements to the new
  // positions accordingly.
  //
  // Note that for better performance instead of
  // find_first_non_full(common(), hash),
  // HashSetResizeHelper::FindFirstNonFullAfterResize(
  //    common(), old_capacity, hash)
  // can be called right after `resize`.
  void resize(size_t new_capacity) {
    raw_hash_set::resize_impl(common(), new_capacity, HashtablezInfoHandle{});
  }

  // As above, except that we also accept a pre-sampled, forced infoz for
  // SOO tables, since they need to switch from SOO to heap in order to
  // store the infoz.
  void resize_with_soo_infoz(HashtablezInfoHandle forced_infoz) {
    assert(forced_infoz.IsSampled());
    raw_hash_set::resize_impl(common(), NextCapacity(SooCapacity()),
                              forced_infoz);
  }

  // Resizes set to the new capacity.
  // It is a static function in order to use its pointer in GetPolicyFunctions.
  Y_ABSL_ATTRIBUTE_NOINLINE static void resize_impl(
      CommonFields& common, size_t new_capacity,
      HashtablezInfoHandle forced_infoz) {
    raw_hash_set* set = reinterpret_cast<raw_hash_set*>(&common);
    assert(IsValidCapacity(new_capacity));
    assert(!set->fits_in_soo(new_capacity));
    const bool was_soo = set->is_soo();
    const bool had_soo_slot = was_soo && !set->empty();
    const ctrl_t soo_slot_h2 =
        had_soo_slot ? static_cast<ctrl_t>(H2(set->hash_of(set->soo_slot())))
                     : ctrl_t::kEmpty;
    HashSetResizeHelper resize_helper(common, was_soo, had_soo_slot,
                                      forced_infoz);
    // Initialize HashSetResizeHelper::old_heap_or_soo_. We can't do this in
    // HashSetResizeHelper constructor because it can't transfer slots when
    // transfer_uses_memcpy is false.
    // TODO(b/289225379): try to handle more of the SOO cases inside
    // InitializeSlots. See comment on cl/555990034 snapshot #63.
    if (PolicyTraits::transfer_uses_memcpy() || !had_soo_slot) {
      resize_helper.old_heap_or_soo() = common.heap_or_soo();
    } else {
      set->transfer(set->to_slot(resize_helper.old_soo_data()),
                    set->soo_slot());
    }
    common.set_capacity(new_capacity);
    // Note that `InitializeSlots` does different number initialization steps
    // depending on the values of `transfer_uses_memcpy` and capacities.
    // Refer to the comment in `InitializeSlots` for more details.
    const bool grow_single_group =
        resize_helper.InitializeSlots<CharAlloc, sizeof(slot_type),
                                      PolicyTraits::transfer_uses_memcpy(),
                                      SooEnabled(), alignof(slot_type)>(
            common, CharAlloc(set->alloc_ref()), soo_slot_h2, sizeof(key_type),
            sizeof(value_type));

    // In the SooEnabled() case, capacity is never 0 so we don't check.
    if (!SooEnabled() && resize_helper.old_capacity() == 0) {
      // InitializeSlots did all the work including infoz().RecordRehash().
      return;
    }
    assert(resize_helper.old_capacity() > 0);
    // Nothing more to do in this case.
    if (was_soo && !had_soo_slot) return;

    slot_type* new_slots = set->slot_array();
    if (grow_single_group) {
      if (PolicyTraits::transfer_uses_memcpy()) {
        // InitializeSlots did all the work.
        return;
      }
      if (was_soo) {
        set->transfer(new_slots + resize_helper.SooSlotIndex(),
                      to_slot(resize_helper.old_soo_data()));
        return;
      } else {
        // We want GrowSizeIntoSingleGroup to be called here in order to make
        // InitializeSlots not depend on PolicyTraits.
        resize_helper.GrowSizeIntoSingleGroup<PolicyTraits>(common,
                                                            set->alloc_ref());
      }
    } else {
      // InitializeSlots prepares control bytes to correspond to empty table.
      const auto insert_slot = [&](slot_type* slot) {
        size_t hash = PolicyTraits::apply(HashElement{set->hash_ref()},
                                          PolicyTraits::element(slot));
        auto target = find_first_non_full(common, hash);
        SetCtrl(common, target.offset, H2(hash), sizeof(slot_type));
        set->transfer(new_slots + target.offset, slot);
        return target.probe_length;
      };
      if (was_soo) {
        insert_slot(to_slot(resize_helper.old_soo_data()));
        return;
      } else {
        auto* old_slots = static_cast<slot_type*>(resize_helper.old_slots());
        size_t total_probe_length = 0;
        for (size_t i = 0; i != resize_helper.old_capacity(); ++i) {
          if (IsFull(resize_helper.old_ctrl()[i])) {
            total_probe_length += insert_slot(old_slots + i);
          }
        }
        common.infoz().RecordRehash(total_probe_length);
      }
    }
    resize_helper.DeallocateOld<alignof(slot_type)>(CharAlloc(set->alloc_ref()),
                                                    sizeof(slot_type));
  }

  // Casting directly from e.g. char* to slot_type* can cause compilation errors
  // on objective-C. This function converts to void* first, avoiding the issue.
  static slot_type* to_slot(void* buf) { return static_cast<slot_type*>(buf); }

  // Requires that lhs does not have a full SOO slot.
  static void move_common(bool that_is_full_soo, allocator_type& rhs_alloc,
                          CommonFields& lhs, CommonFields&& rhs) {
    if (PolicyTraits::transfer_uses_memcpy() || !that_is_full_soo) {
      lhs = std::move(rhs);
    } else {
      lhs.move_non_heap_or_soo_fields(rhs);
      // TODO(b/303305702): add reentrancy guard.
      PolicyTraits::transfer(&rhs_alloc, to_slot(lhs.soo_data()),
                             to_slot(rhs.soo_data()));
    }
  }

  // Swaps common fields making sure to avoid memcpy'ing a full SOO slot if we
  // aren't allowed to do so.
  void swap_common(raw_hash_set& that) {
    using std::swap;
    if (PolicyTraits::transfer_uses_memcpy()) {
      swap(common(), that.common());
      return;
    }
    CommonFields tmp = CommonFields::CreateDefault<SooEnabled()>();
    const bool that_is_full_soo = that.is_full_soo();
    move_common(that_is_full_soo, that.alloc_ref(), tmp,
                std::move(that.common()));
    move_common(is_full_soo(), alloc_ref(), that.common(), std::move(common()));
    move_common(that_is_full_soo, that.alloc_ref(), common(), std::move(tmp));
  }

  void maybe_increment_generation_or_rehash_on_move() {
    if (!SwisstableGenerationsEnabled() || capacity() == 0 || is_soo()) {
      return;
    }
    common().increment_generation();
    if (!empty() && common().should_rehash_for_bug_detection_on_move()) {
      resize(capacity());
    }
  }

  template <bool propagate_alloc>
  raw_hash_set& assign_impl(raw_hash_set&& that) {
    // We don't bother checking for this/that aliasing. We just need to avoid
    // breaking the invariants in that case.
    destructor_impl();
    move_common(that.is_full_soo(), that.alloc_ref(), common(),
                std::move(that.common()));
    // TODO(b/296061262): move instead of copying hash/eq/alloc.
    hash_ref() = that.hash_ref();
    eq_ref() = that.eq_ref();
    CopyAlloc(alloc_ref(), that.alloc_ref(),
              std::integral_constant<bool, propagate_alloc>());
    that.common() = CommonFields::CreateDefault<SooEnabled()>();
    maybe_increment_generation_or_rehash_on_move();
    return *this;
  }

  raw_hash_set& move_elements_allocs_unequal(raw_hash_set&& that) {
    const size_t size = that.size();
    if (size == 0) return *this;
    reserve(size);
    for (iterator it = that.begin(); it != that.end(); ++it) {
      insert(std::move(PolicyTraits::element(it.slot())));
      that.destroy(it.slot());
    }
    if (!that.is_soo()) that.dealloc();
    that.common() = CommonFields::CreateDefault<SooEnabled()>();
    maybe_increment_generation_or_rehash_on_move();
    return *this;
  }

  raw_hash_set& move_assign(raw_hash_set&& that,
                            std::true_type /*propagate_alloc*/) {
    return assign_impl<true>(std::move(that));
  }
  raw_hash_set& move_assign(raw_hash_set&& that,
                            std::false_type /*propagate_alloc*/) {
    if (alloc_ref() == that.alloc_ref()) {
      return assign_impl<false>(std::move(that));
    }
    // Aliasing can't happen here because allocs would compare equal above.
    assert(this != &that);
    destructor_impl();
    // We can't take over that's memory so we need to move each element.
    // While moving elements, this should have that's hash/eq so copy hash/eq
    // before moving elements.
    // TODO(b/296061262): move instead of copying hash/eq.
    hash_ref() = that.hash_ref();
    eq_ref() = that.eq_ref();
    return move_elements_allocs_unequal(std::move(that));
  }

  template <class K>
  std::pair<iterator, bool> find_or_prepare_insert_soo(const K& key) {
    if (empty()) {
      const HashtablezInfoHandle infoz = try_sample_soo();
      if (infoz.IsSampled()) {
        resize_with_soo_infoz(infoz);
      } else {
        common().set_full_soo();
        return {soo_iterator(), true};
      }
    } else if (PolicyTraits::apply(EqualElement<K>{key, eq_ref()},
                                   PolicyTraits::element(soo_slot()))) {
      return {soo_iterator(), false};
    } else {
      resize(NextCapacity(SooCapacity()));
    }
    const size_t index =
        PrepareInsertAfterSoo(hash_ref()(key), sizeof(slot_type), common());
    return {iterator_at(index), true};
  }

  template <class K>
  std::pair<iterator, bool> find_or_prepare_insert_non_soo(const K& key) {
    assert(!is_soo());
    prefetch_heap_block();
    auto hash = hash_ref()(key);
    auto seq = probe(common(), hash);
    const ctrl_t* ctrl = control();
    while (true) {
      Group g{ctrl + seq.offset()};
      for (uint32_t i : g.Match(H2(hash))) {
        if (Y_ABSL_PREDICT_TRUE(PolicyTraits::apply(
                EqualElement<K>{key, eq_ref()},
                PolicyTraits::element(slot_array() + seq.offset(i)))))
          return {iterator_at(seq.offset(i)), false};
      }
      auto mask_empty = g.MaskEmpty();
      if (Y_ABSL_PREDICT_TRUE(mask_empty)) {
        size_t target = seq.offset(
            GetInsertionOffset(mask_empty, capacity(), hash, control()));
        return {iterator_at(PrepareInsertNonSoo(common(), hash,
                                                FindInfo{target, seq.index()},
                                                GetPolicyFunctions())),
                true};
      }
      seq.next();
      assert(seq.index() <= capacity() && "full table!");
    }
  }

 protected:
  // Asserts that hash and equal functors provided by the user are consistent,
  // meaning that `eq(k1, k2)` implies `hash(k1)==hash(k2)`.
  template <class K>
  void AssertHashEqConsistent(Y_ABSL_ATTRIBUTE_UNUSED const K& key) {
#ifndef NDEBUG
    if (empty()) return;

    const size_t hash_of_arg = hash_ref()(key);
    const auto assert_consistent = [&](const ctrl_t*, slot_type* slot) {
      const value_type& element = PolicyTraits::element(slot);
      const bool is_key_equal =
          PolicyTraits::apply(EqualElement<K>{key, eq_ref()}, element);
      if (!is_key_equal) return;

      const size_t hash_of_slot =
          PolicyTraits::apply(HashElement{hash_ref()}, element);
      const bool is_hash_equal = hash_of_arg == hash_of_slot;
      if (!is_hash_equal) {
        // In this case, we're going to crash. Do a couple of other checks for
        // idempotence issues. Recalculating hash/eq here is also convenient for
        // debugging with gdb/lldb.
        const size_t once_more_hash_arg = hash_ref()(key);
        assert(hash_of_arg == once_more_hash_arg && "hash is not idempotent.");
        const size_t once_more_hash_slot =
            PolicyTraits::apply(HashElement{hash_ref()}, element);
        assert(hash_of_slot == once_more_hash_slot &&
               "hash is not idempotent.");
        const bool once_more_eq =
            PolicyTraits::apply(EqualElement<K>{key, eq_ref()}, element);
        assert(is_key_equal == once_more_eq && "equality is not idempotent.");
      }
      assert((!is_key_equal || is_hash_equal) &&
             "eq(k1, k2) must imply that hash(k1) == hash(k2). "
             "hash/eq functors are inconsistent.");
    };

    if (is_soo()) {
      assert_consistent(/*unused*/ nullptr, soo_slot());
      return;
    }
    // We only do validation for small tables so that it's constant time.
    if (capacity() > 16) return;
    IterateOverFullSlots(common(), slot_array(), assert_consistent);
#endif
  }

  // Attempts to find `key` in the table; if it isn't found, returns an iterator
  // where the value can be inserted into, with the control byte already set to
  // `key`'s H2. Returns a bool indicating whether an insertion can take place.
  template <class K>
  std::pair<iterator, bool> find_or_prepare_insert(const K& key) {
    AssertHashEqConsistent(key);
    if (is_soo()) return find_or_prepare_insert_soo(key);
    return find_or_prepare_insert_non_soo(key);
  }

  // Constructs the value in the space pointed by the iterator. This only works
  // after an unsuccessful find_or_prepare_insert() and before any other
  // modifications happen in the raw_hash_set.
  //
  // PRECONDITION: iter was returned from find_or_prepare_insert(k), where k is
  // the key decomposed from `forward<Args>(args)...`, and the bool returned by
  // find_or_prepare_insert(k) was true.
  // POSTCONDITION: *m.iterator_at(i) == value_type(forward<Args>(args)...).
  template <class... Args>
  void emplace_at(iterator iter, Args&&... args) {
    construct(iter.slot(), std::forward<Args>(args)...);

    assert(PolicyTraits::apply(FindElement{*this}, *iter) == iter &&
           "constructed value does not match the lookup key");
  }

  iterator iterator_at(size_t i) Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return {control() + i, slot_array() + i, common().generation_ptr()};
  }
  const_iterator iterator_at(size_t i) const Y_ABSL_ATTRIBUTE_LIFETIME_BOUND {
    return const_cast<raw_hash_set*>(this)->iterator_at(i);
  }

  reference unchecked_deref(iterator it) { return it.unchecked_deref(); }

 private:
  friend struct RawHashSetTestOnlyAccess;

  // The number of slots we can still fill without needing to rehash.
  //
  // This is stored separately due to tombstones: we do not include tombstones
  // in the growth capacity, because we'd like to rehash when the table is
  // otherwise filled with tombstones: otherwise, probe sequences might get
  // unacceptably long without triggering a rehash. Callers can also force a
  // rehash via the standard `rehash(0)`, which will recompute this value as a
  // side-effect.
  //
  // See `CapacityToGrowth()`.
  size_t growth_left() const {
    assert(!is_soo());
    return common().growth_left();
  }

  GrowthInfo& growth_info() {
    assert(!is_soo());
    return common().growth_info();
  }
  GrowthInfo growth_info() const {
    assert(!is_soo());
    return common().growth_info();
  }

  // Prefetch the heap-allocated memory region to resolve potential TLB and
  // cache misses. This is intended to overlap with execution of calculating the
  // hash for a key.
  void prefetch_heap_block() const {
    assert(!is_soo());
#if Y_ABSL_HAVE_BUILTIN(__builtin_prefetch) || defined(__GNUC__)
    __builtin_prefetch(control(), 0, 1);
#endif
  }

  CommonFields& common() { return settings_.template get<0>(); }
  const CommonFields& common() const { return settings_.template get<0>(); }

  ctrl_t* control() const {
    assert(!is_soo());
    return common().control();
  }
  slot_type* slot_array() const {
    assert(!is_soo());
    return static_cast<slot_type*>(common().slot_array());
  }
  slot_type* soo_slot() {
    assert(is_soo());
    return static_cast<slot_type*>(common().soo_data());
  }
  const slot_type* soo_slot() const {
    return const_cast<raw_hash_set*>(this)->soo_slot();
  }
  iterator soo_iterator() {
    return {SooControl(), soo_slot(), common().generation_ptr()};
  }
  const_iterator soo_iterator() const {
    return const_cast<raw_hash_set*>(this)->soo_iterator();
  }
  HashtablezInfoHandle infoz() {
    assert(!is_soo());
    return common().infoz();
  }

  hasher& hash_ref() { return settings_.template get<1>(); }
  const hasher& hash_ref() const { return settings_.template get<1>(); }
  key_equal& eq_ref() { return settings_.template get<2>(); }
  const key_equal& eq_ref() const { return settings_.template get<2>(); }
  allocator_type& alloc_ref() { return settings_.template get<3>(); }
  const allocator_type& alloc_ref() const {
    return settings_.template get<3>();
  }

  static const void* get_hash_ref_fn(const CommonFields& common) {
    auto* h = reinterpret_cast<const raw_hash_set*>(&common);
    return &h->hash_ref();
  }
  static void transfer_slot_fn(void* set, void* dst, void* src) {
    auto* h = static_cast<raw_hash_set*>(set);
    h->transfer(static_cast<slot_type*>(dst), static_cast<slot_type*>(src));
  }
  // Note: dealloc_fn will only be used if we have a non-standard allocator.
  static void dealloc_fn(CommonFields& common, const PolicyFunctions&) {
    auto* set = reinterpret_cast<raw_hash_set*>(&common);

    // Unpoison before returning the memory to the allocator.
    SanitizerUnpoisonMemoryRegion(common.slot_array(),
                                  sizeof(slot_type) * common.capacity());

    common.infoz().Unregister();
    Deallocate<BackingArrayAlignment(alignof(slot_type))>(
        &set->alloc_ref(), common.backing_array_start(),
        common.alloc_size(sizeof(slot_type), alignof(slot_type)));
  }

  static const PolicyFunctions& GetPolicyFunctions() {
    static constexpr PolicyFunctions value = {
        sizeof(slot_type),
        // TODO(b/328722020): try to type erase
        // for standard layout and alignof(Hash) <= alignof(CommonFields).
        std::is_empty<hasher>::value ? &GetHashRefForEmptyHasher
                                     : &raw_hash_set::get_hash_ref_fn,
        PolicyTraits::template get_hash_slot_fn<hasher>(),
        PolicyTraits::transfer_uses_memcpy()
            ? TransferRelocatable<sizeof(slot_type)>
            : &raw_hash_set::transfer_slot_fn,
        (std::is_same<SlotAlloc, std::allocator<slot_type>>::value
             ? &DeallocateStandard<alignof(slot_type)>
             : &raw_hash_set::dealloc_fn),
        &raw_hash_set::resize_impl,
    };
    return value;
  }

  // Bundle together CommonFields plus other objects which might be empty.
  // CompressedTuple will ensure that sizeof is not affected by any of the empty
  // fields that occur after CommonFields.
  y_absl::container_internal::CompressedTuple<CommonFields, hasher, key_equal,
                                            allocator_type>
      settings_{CommonFields::CreateDefault<SooEnabled()>(), hasher{},
                key_equal{}, allocator_type{}};
};

// Friend access for free functions in raw_hash_set.h.
struct HashtableFreeFunctionsAccess {
  template <class Predicate, typename Set>
  static typename Set::size_type EraseIf(Predicate& pred, Set* c) {
    if (c->empty()) {
      return 0;
    }
    if (c->is_soo()) {
      auto it = c->soo_iterator();
      if (!pred(*it)) {
        assert(c->size() == 1 && "hash table was modified unexpectedly");
        return 0;
      }
      c->destroy(it.slot());
      c->common().set_empty_soo();
      return 1;
    }
    Y_ABSL_ATTRIBUTE_UNUSED const size_t original_size_for_assert = c->size();
    size_t num_deleted = 0;
    IterateOverFullSlots(
        c->common(), c->slot_array(), [&](const ctrl_t* ctrl, auto* slot) {
          if (pred(Set::PolicyTraits::element(slot))) {
            c->destroy(slot);
            EraseMetaOnly(c->common(), static_cast<size_t>(ctrl - c->control()),
                          sizeof(*slot));
            ++num_deleted;
          }
        });
    // NOTE: IterateOverFullSlots allow removal of the current element, so we
    // verify the size additionally here.
    assert(original_size_for_assert - num_deleted == c->size() &&
           "hash table was modified unexpectedly");
    return num_deleted;
  }

  template <class Callback, typename Set>
  static void ForEach(Callback& cb, Set* c) {
    if (c->empty()) {
      return;
    }
    if (c->is_soo()) {
      cb(*c->soo_iterator());
      return;
    }
    using ElementTypeWithConstness = decltype(*c->begin());
    IterateOverFullSlots(
        c->common(), c->slot_array(), [&cb](const ctrl_t*, auto* slot) {
          ElementTypeWithConstness& element = Set::PolicyTraits::element(slot);
          cb(element);
        });
  }
};

// Erases all elements that satisfy the predicate `pred` from the container `c`.
template <typename P, typename H, typename E, typename A, typename Predicate>
typename raw_hash_set<P, H, E, A>::size_type EraseIf(
    Predicate& pred, raw_hash_set<P, H, E, A>* c) {
  return HashtableFreeFunctionsAccess::EraseIf(pred, c);
}

// Calls `cb` for all elements in the container `c`.
template <typename P, typename H, typename E, typename A, typename Callback>
void ForEach(Callback& cb, raw_hash_set<P, H, E, A>* c) {
  return HashtableFreeFunctionsAccess::ForEach(cb, c);
}
template <typename P, typename H, typename E, typename A, typename Callback>
void ForEach(Callback& cb, const raw_hash_set<P, H, E, A>* c) {
  return HashtableFreeFunctionsAccess::ForEach(cb, c);
}

namespace hashtable_debug_internal {
template <typename Set>
struct HashtableDebugAccess<Set, y_absl::void_t<typename Set::raw_hash_set>> {
  using Traits = typename Set::PolicyTraits;
  using Slot = typename Traits::slot_type;

  static size_t GetNumProbes(const Set& set,
                             const typename Set::key_type& key) {
    if (set.is_soo()) return 0;
    size_t num_probes = 0;
    size_t hash = set.hash_ref()(key);
    auto seq = probe(set.common(), hash);
    const ctrl_t* ctrl = set.control();
    while (true) {
      container_internal::Group g{ctrl + seq.offset()};
      for (uint32_t i : g.Match(container_internal::H2(hash))) {
        if (Traits::apply(
                typename Set::template EqualElement<typename Set::key_type>{
                    key, set.eq_ref()},
                Traits::element(set.slot_array() + seq.offset(i))))
          return num_probes;
        ++num_probes;
      }
      if (g.MaskEmpty()) return num_probes;
      seq.next();
      ++num_probes;
    }
  }

  static size_t AllocatedByteSize(const Set& c) {
    size_t capacity = c.capacity();
    if (capacity == 0) return 0;
    size_t m =
        c.is_soo() ? 0 : c.common().alloc_size(sizeof(Slot), alignof(Slot));

    size_t per_slot = Traits::space_used(static_cast<const Slot*>(nullptr));
    if (per_slot != ~size_t{}) {
      m += per_slot * c.size();
    } else {
      for (auto it = c.begin(); it != c.end(); ++it) {
        m += Traits::space_used(it.slot());
      }
    }
    return m;
  }
};

}  // namespace hashtable_debug_internal
}  // namespace container_internal
Y_ABSL_NAMESPACE_END
}  // namespace y_absl

#undef Y_ABSL_SWISSTABLE_ENABLE_GENERATIONS
#undef Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED
#undef Y_ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN

#endif  // Y_ABSL_CONTAINER_INTERNAL_RAW_HASH_SET_H_