Name Generator 1.0.0
Culture-aware name generation for C++23
Loading...
Searching...
No Matches
namegen.hpp
Go to the documentation of this file.
1#ifndef DASMIG_NAMEGEN_HPP
2#define DASMIG_NAMEGEN_HPP
3
4#include "random.hpp"
5#include <algorithm>
6#include <array>
7#include <cstddef>
8#include <cstdint>
9#include <filesystem>
10#include <fstream>
11#include <map>
12#include <ostream>
13#include <random>
14#include <stdexcept>
15#include <string>
16#include <utility>
17#include <vector>
18
19/// @file namegen.hpp
20/// @brief Name generator library — culture-aware name generation for C++23.
21/// @author Diego Dasso Migotto (diegomigotto at hotmail dot com)
22/// @see See doc/usage.md for the narrative tutorial.
23
24struct ng_test_access;
25
26namespace dasmig
27{
28
29/// @brief Culture representing a country or a broader group.
30enum class culture : std::uint8_t
31{
32 american,
33 argentinian,
34 australian,
35 brazilian,
36 british,
37 bulgarian,
38 canadian,
39 chinese,
40 danish,
41 finnish,
42 french,
43 german,
44 kazakh,
45 mexican,
46 norwegian,
47 polish,
48 portuguese,
49 russian,
50 spanish,
51 swedish,
52 turkish,
53 ukrainian,
54 any
55};
56
57/// @brief Simple gender enum to distinguish between male and female names.
58enum class gender : std::uint8_t
59{
60 m,
61 f,
62 any
63};
64
65/// @brief Return type for name generation, holding both individual parts and
66/// the full composed string.
67///
68/// Supports implicit conversion to std::wstring, streaming via operator<<,
69/// and chained appending of names and surnames.
70class name
71{
72 public:
73 /// @brief Retrieve the random seed used to generate this name.
74 /// @return The per-call seed for replay.
75 /// @see ng::get_name(gender, culture, std::uint64_t)
76 [[nodiscard]] std::uint64_t seed() const
77 {
78 return _seed;
79 }
80
81 /// @brief Return the individual parts (names/surnames) as a vector.
82 /// @return Vector of name parts.
83 [[nodiscard]] const std::vector<std::wstring>& parts() const
84 {
85 return _parts;
86 }
87
88 /// @brief Append a forename to this name, preserving gender and culture.
89 /// @return `*this` for chaining.
91
92 /// @brief Append a forename of a specific culture.
93 /// @param c Culture for the appended name.
94 /// @return `*this` for chaining.
96
97 /// @brief Append a surname to this name, preserving culture.
98 /// @return `*this` for chaining.
100
101 /// @brief Append a surname of a specific culture.
102 /// @param c Culture for the appended surname.
103 /// @return `*this` for chaining.
105
106 /// @brief Implicit conversion to std::wstring.
107 /// @return The full composed name string.
108 operator std::wstring() const // NOLINT(hicpp-explicit-conversions)
109 {
110 return _full_string;
111 }
112
113 /// @brief Implicit conversion to a vector of name parts.
114 operator std::vector<std::wstring>() const // NOLINT(hicpp-explicit-conversions)
115 {
116 return _parts;
117 }
118
119 /// @brief Stream the name to a wide output stream.
120 /// @param wos Output stream.
121 /// @param n Name to stream.
122 /// @return Reference to the output stream.
123 friend std::wostream& operator<<(std::wostream& wos, const name& n)
124 {
125 wos << n._full_string;
126 return wos;
127 }
128
129 private:
130 // Private constructor — names are created only by ng.
131 name(std::wstring name_str, gender g, culture c, class ng* owner,
132 std::uint64_t seed = 0)
133 : _full_string(std::move(name_str)), _gender(g), _culture(c),
134 _owner(owner), _seed(seed)
135 {
136 _parts.push_back(_full_string);
137 }
138
139 std::wstring _full_string; ///< Full composed name.
140 std::vector<std::wstring> _parts; ///< Individual name parts.
141 gender _gender; ///< Gender of the first name.
142 culture _culture; ///< Culture of the first name.
143 class ng* _owner; ///< Generator that created this name.
144 std::uint64_t _seed{0}; ///< Random seed for replay.
145
146 friend class ng;
147};
148
149/// @brief Name generator that produces culture-aware names and surnames.
150///
151/// Generates realistic names by picking from popular name databases indexed
152/// by culture and gender. Supports 23 cultures.
153///
154/// Can be used as a singleton via instance() or constructed independently.
155/// Independent instances own their own name databases and random engine.
156///
157/// @par Thread safety
158/// Each instance is independent. Concurrent calls to get_name() on
159/// the **same** instance require external synchronization. load() mutates
160/// internal state and must not be called concurrently with get_name()
161/// on the same instance.
162class ng
163{
164 public:
165 /// @brief Default constructor — creates an empty generator with no names.
166 ///
167 /// Call load() to populate name databases before generating.
168 ng() = default;
169
170 ng(const ng&) = delete; ///< Not copyable.
171 ng& operator=(const ng&) = delete; ///< Not copyable.
172 ng(ng&&) noexcept = default; ///< Move constructor.
173 ng& operator=(ng&&) noexcept = default; ///< Move assignment.
174 ~ng() = default; ///< Default destructor.
175
176 /// @brief Access the global singleton instance.
177 ///
178 /// The singleton auto-probes common resource paths on first access.
179 /// For independent generators, prefer constructing a separate ng instance.
180 /// @return Reference to the global ng instance.
181 static ng& instance()
182 {
183 static ng inst{auto_probe_tag{}};
184 return inst;
185 }
186
187 /// @brief Translate an ISO 3166 2-letter country code to a culture enum.
188 /// @param country_code Two-letter country code (e.g., L"us", L"br").
189 /// @return Matching culture, or culture::any if not recognized.
190 [[nodiscard]] static culture to_culture(const std::wstring& country_code)
191 {
192 static const std::map<std::wstring, culture> country_code_map = {
193 {L"ar", culture::argentinian}, {L"us", culture::american},
194 {L"au", culture::australian}, {L"br", culture::brazilian},
195 {L"gb", culture::british}, {L"bg", culture::bulgarian},
196 {L"ca", culture::canadian}, {L"cn", culture::chinese},
197 {L"dk", culture::danish}, {L"fi", culture::finnish},
198 {L"fr", culture::french}, {L"de", culture::german},
199 {L"kz", culture::kazakh}, {L"mx", culture::mexican},
200 {L"no", culture::norwegian}, {L"pl", culture::polish},
201 {L"pt", culture::portuguese}, {L"ru", culture::russian},
202 {L"es", culture::spanish}, {L"se", culture::swedish},
203 {L"tr", culture::turkish}, {L"ua", culture::ukrainian}};
204
205 if (auto it = country_code_map.find(country_code);
206 it != country_code_map.end())
207 {
208 return it->second;
209 }
210 return culture::any;
211 }
212
213 /// @brief Translate a gender string to a gender enum.
214 /// @param gender_string Gender string (e.g., L"male", L"female", L"m", L"f").
215 /// @return Matching gender, or gender::any if not recognized.
216 [[nodiscard]] static gender to_gender(const std::wstring& gender_string)
217 {
218 static const std::map<std::wstring, gender> gender_map = {
219 {L"m", gender::m},
220 {L"f", gender::f},
221 {L"male", gender::m},
222 {L"female", gender::f}};
223
224 if (auto it = gender_map.find(gender_string);
225 it != gender_map.end())
226 {
227 return it->second;
228 }
229 return gender::any;
230 }
231
232 /// @brief Generate a first name.
233 /// @param g Gender (default: random).
234 /// @param c Culture (default: random).
235 /// @return A name object supporting chained appending.
236 /// @throws std::invalid_argument If no names loaded for the resolved culture/gender.
237 [[nodiscard]] name get_name(gender g = gender::any,
238 culture c = culture::any)
239 {
240 auto call_seed = static_cast<std::uint64_t>(_engine());
241 auto result = solver(true, g, c, call_seed);
242 result._seed = call_seed;
243 return result;
244 }
245
246 /// @brief Generate a deterministic first name using a specific seed.
247 /// @param g Gender (default: random).
248 /// @param c Culture (default: random).
249 /// @param call_seed Seed for reproducible results.
250 /// @return A name object.
251 /// @throws std::invalid_argument If no names loaded for the resolved culture/gender.
252 [[nodiscard]] name get_name(gender g, culture c,
253 std::uint64_t call_seed)
254 {
255 auto result = solver(true, g, c, call_seed);
256 result._seed = call_seed;
257 return result;
258 }
259
260 /// @brief Generate a surname.
261 /// @param c Culture (default: random).
262 /// @return A name object supporting chained appending.
263 /// @throws std::invalid_argument If no surnames loaded for the resolved culture.
264 [[nodiscard]] name get_surname(culture c = culture::any)
265 {
266 auto call_seed = static_cast<std::uint64_t>(_engine());
267 auto result = solver(false, gender::any, c, call_seed);
268 result._seed = call_seed;
269 return result;
270 }
271
272 /// @brief Generate a deterministic surname using a specific seed.
273 /// @param c Culture (default: random).
274 /// @param call_seed Seed for reproducible results.
275 /// @return A name object.
276 /// @throws std::invalid_argument If no surnames loaded for the resolved culture.
277 [[nodiscard]] name get_surname(culture c,
278 std::uint64_t call_seed)
279 {
280 auto result = solver(false, gender::any, c, call_seed);
281 result._seed = call_seed;
282 return result;
283 }
284
285 /// @name Seeding
286 /// @{
287
288 /// @brief Seed the internal random engine for deterministic sequences.
289 ///
290 /// Subsequent get_name() / get_surname() calls (without an explicit seed)
291 /// draw per-call seeds from this engine, producing a reproducible sequence.
292 ///
293 /// @param seed_value The seed value.
294 /// @return `*this` for chaining.
295 ng& seed(std::uint64_t seed_value)
296 {
297 _engine.seed(seed_value);
298 return *this;
299 }
300
301 /// @brief Reseed the engine with a non-deterministic source.
302 /// @return `*this` for chaining.
304 {
305 _engine.seed(std::random_device{}());
306 return *this;
307 }
308
309 /// @}
310
311 /// @brief Check whether any name databases have been loaded.
312 /// @return `true` if at least one name file has been loaded.
313 [[nodiscard]] bool has_resources() const
314 {
315 return !_culture_indexed_m_names.empty() ||
316 !_culture_indexed_f_names.empty() ||
317 !_culture_indexed_surnames.empty();
318 }
319
320 /// @brief Load name files from a directory.
321 ///
322 /// Recursively scans @p resource_path for `.names` files and indexes them
323 /// by culture and gender. Safe to call multiple times.
324 ///
325 /// @param resource_path Directory containing `.names` files.
326 void load(const std::filesystem::path& resource_path)
327 {
328 if (std::filesystem::exists(resource_path) &&
329 std::filesystem::is_directory(resource_path))
330 {
331 for (const auto& entry :
332 std::filesystem::recursive_directory_iterator(resource_path))
333 {
334 if (entry.is_regular_file() &&
335 (entry.path().extension() == ".names"))
336 {
337 parse_file(entry);
338 }
339 }
340 }
341 }
342
343 private:
344 // Container of names.
345 using name_container = std::vector<std::wstring>;
346
347 // Number of concrete cultures (excluding `any`).
348 static constexpr std::size_t culture_count =
349 static_cast<std::size_t>(culture::any);
350
351 // Maps for accessing names through culture.
352 std::map<culture, name_container> _culture_indexed_m_names;
353 std::map<culture, name_container> _culture_indexed_f_names;
354 std::map<culture, name_container> _culture_indexed_surnames;
355
356 // Per-instance random engine for seed drawing.
357 std::mt19937_64 _engine{std::random_device{}()};
358
359 // Tag type for the auto-probing singleton constructor.
360 struct auto_probe_tag {};
361
362 // Singleton constructor: auto-probes common resource locations.
363 explicit ng(auto_probe_tag /*tag*/)
364 {
365 static constexpr std::array probe_paths = {
366 "resources", "../resources", "name-generator/resources"};
367
368 auto found = std::ranges::find_if(probe_paths, [](const char* p) {
369 return std::filesystem::exists(p) &&
370 std::filesystem::is_directory(p);
371 });
372 if (found != probe_paths.end())
373 {
374 load(*found);
375 }
376 }
377
378 // Resolve `any` culture to a concrete random culture.
379 static culture resolve_culture(culture c,
380 effolkronium::random_local& engine)
381 {
382 if (c == culture::any)
383 {
384 return static_cast<culture>(
385 engine.get<std::size_t>(0, culture_count - 1));
386 }
387 return c;
388 }
389
390 // Resolve `any` gender to a concrete random gender.
391 static gender resolve_gender(gender g,
392 effolkronium::random_local& engine)
393 {
394 if (g == gender::any)
395 {
396 return static_cast<gender>(engine.get<std::size_t>(0, 1));
397 }
398 return g;
399 }
400
401 // Convert a culture enum to its display name for error messages.
402 [[nodiscard]] static const char* culture_label(culture c)
403 {
404 static constexpr std::array labels = {
405 "american", "argentinian", "australian",
406 "brazilian", "british", "bulgarian",
407 "canadian", "chinese", "danish",
408 "finnish", "french", "german",
409 "kazakh", "mexican", "norwegian",
410 "polish", "portuguese", "russian",
411 "spanish", "swedish", "turkish",
412 "ukrainian", "any"};
413 auto idx = static_cast<std::size_t>(c);
414 if (idx < labels.size()) { return labels.at(idx); }
415 return "unknown";
416 }
417
418 // Convert a gender enum to its display name for error messages.
419 [[nodiscard]] static const char* gender_label(gender g)
420 {
421 switch (g)
422 {
423 case gender::m: return "male";
424 case gender::f: return "female";
425 case gender::any: return "any";
426 }
427 return "unknown";
428 }
429
430 // Pick a random name/surname from the appropriate map.
431 [[nodiscard]] static std::wstring pick(
432 const std::map<culture, name_container>& db,
433 culture c, gender g, effolkronium::random_local& engine)
434 {
435 if (auto it = db.find(c); it != db.end() && !it->second.empty())
436 {
437 return *engine.get(it->second);
438 }
439 throw std::invalid_argument(
440 std::string("No ") + gender_label(g) +
441 " names loaded for culture '" + culture_label(c) + "'");
442 }
443
444 /// @brief Number of bits to shift when XOR-folding a 64-bit seed to 32.
445 static constexpr unsigned seed_fold_shift = 32U;
446
447 // Append a forename part to an existing name.
448 void append_name_impl(name& n, gender g, culture c)
449 {
450 auto call_seed = static_cast<std::uint64_t>(_engine());
451 effolkronium::random_local call_engine;
452 call_engine.seed(static_cast<std::mt19937::result_type>(
453 (call_seed ^ (call_seed >> seed_fold_shift))));
454
455 const culture resolved_c = resolve_culture(c, call_engine);
456 const gender resolved_g = resolve_gender(g, call_engine);
457
458 const auto& db = (resolved_g == gender::f)
459 ? _culture_indexed_f_names
460 : _culture_indexed_m_names;
461
462 const std::wstring part = pick(db, resolved_c, resolved_g,
463 call_engine);
464 n._parts.push_back(part);
465 n._full_string.append(L" ").append(part);
466 }
467
468 // Append a surname part to an existing name.
469 void append_surname_impl(name& n, culture c)
470 {
471 auto call_seed = static_cast<std::uint64_t>(_engine());
472 effolkronium::random_local call_engine;
473 call_engine.seed(static_cast<std::mt19937::result_type>(
474 (call_seed ^ (call_seed >> seed_fold_shift))));
475
476 const culture resolved_c = resolve_culture(c, call_engine);
477 const std::wstring part = pick(_culture_indexed_surnames, resolved_c,
478 gender::any, call_engine);
479 n._parts.push_back(part);
480 n._full_string.append(L" ").append(part);
481 }
482
483 // Core generation logic.
484 [[nodiscard]] name solver(bool is_name, gender requested_gender,
485 culture requested_culture,
486 std::uint64_t call_seed)
487 {
488 effolkronium::random_local call_engine;
489 call_engine.seed(static_cast<std::mt19937::result_type>(
490 (call_seed ^ (call_seed >> seed_fold_shift))));
491
492 const culture resolved_culture = resolve_culture(requested_culture,
493 call_engine);
494 const gender resolved_gender = resolve_gender(requested_gender,
495 call_engine);
496
497 if (is_name)
498 {
499 const auto& db = (resolved_gender == gender::f)
500 ? _culture_indexed_f_names
501 : _culture_indexed_m_names;
502 return {pick(db, resolved_culture, resolved_gender, call_engine),
503 resolved_gender, resolved_culture, this};
504 }
505
506 return {pick(_culture_indexed_surnames, resolved_culture,
507 gender::any, call_engine),
508 resolved_gender, resolved_culture, this};
509 }
510
511 /// @brief Decode a UTF-8 byte string into a wide string.
512 ///
513 /// Handles 1–4 byte sequences and produces UTF-32 on Linux
514 /// (wchar_t is 4 bytes) or UTF-16 surrogate pairs on Windows
515 /// (wchar_t is 2 bytes). Invalid lead bytes are silently skipped.
516 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
517 static std::wstring utf8_to_wstring(const std::string& utf8)
518 {
519 // UTF-8 prefix masks and value masks for each sequence length.
520 static constexpr unsigned char ascii_max = 0x80U;
521 static constexpr unsigned char two_byte_mask = 0xE0U;
522 static constexpr unsigned char two_byte_lead = 0xC0U;
523 static constexpr unsigned char two_byte_val = 0x1FU;
524 static constexpr unsigned char three_byte_mask = 0xF0U;
525 static constexpr unsigned char three_byte_lead = 0xE0U;
526 static constexpr unsigned char three_byte_val = 0x0FU;
527 static constexpr unsigned char four_byte_mask = 0xF8U;
528 static constexpr unsigned char four_byte_lead = 0xF0U;
529 static constexpr unsigned char four_byte_val = 0x07U;
530 static constexpr unsigned char cont_val = 0x3FU;
531 static constexpr unsigned char cont_check_mask = 0xC0U;
532 static constexpr unsigned char cont_check_lead = 0x80U;
533 static constexpr unsigned cont_shift = 6U;
534 static constexpr char32_t max_codepoint = 0x10FFFFU;
535 // Surrogate-pair constants (UTF-16, wchar_t == 2 bytes only).
536 static constexpr char32_t surrogate_offset = 0x10000U;
537 static constexpr char32_t high_surrogate_base = 0xD800U;
538 static constexpr char32_t low_surrogate_base = 0xDC00U;
539 static constexpr unsigned surrogate_shift = 10U;
540 static constexpr char32_t surrogate_mask = 0x3FFU;
541
542 std::wstring result;
543 result.reserve(utf8.size());
544 std::size_t i = 0;
545
546 while (i < utf8.size())
547 {
548 char32_t codepoint = 0;
549 auto lead = static_cast<unsigned char>(utf8.at(i));
550 std::size_t extra = 0;
551
552 if (lead < ascii_max)
553 {
554 codepoint = lead;
555 }
556 else if ((lead & two_byte_mask) == two_byte_lead)
557 {
558 codepoint = lead & two_byte_val;
559 extra = 1;
560 }
561 else if ((lead & three_byte_mask) == three_byte_lead)
562 {
563 codepoint = lead & three_byte_val;
564 extra = 2;
565 }
566 else if ((lead & four_byte_mask) == four_byte_lead)
567 {
568 codepoint = lead & four_byte_val;
569 extra = 3;
570 }
571 else
572 {
573 ++i;
574 continue; // skip invalid lead byte
575 }
576
577 ++i;
578 bool valid = true;
579 for (std::size_t j = 0; j < extra; ++j, ++i)
580 {
581 if (i >= utf8.size())
582 {
583 valid = false;
584 break; // truncated sequence
585 }
586 auto byte = static_cast<unsigned char>(utf8.at(i));
587 if ((byte & cont_check_mask) != cont_check_lead)
588 {
589 valid = false;
590 break; // not a valid continuation byte
591 }
592 codepoint = (codepoint << cont_shift) |
593 (static_cast<char32_t>(byte) &
594 static_cast<char32_t>(cont_val));
595 }
596
597 if (!valid || codepoint > max_codepoint)
598 {
599 continue; // skip malformed or out-of-range sequence
600 }
601
602 if constexpr (sizeof(wchar_t) >= 4)
603 {
604 result.push_back(static_cast<wchar_t>(codepoint));
605 }
606 else
607 {
608 if (codepoint < surrogate_offset)
609 {
610 result.push_back(static_cast<wchar_t>(codepoint));
611 }
612 else
613 {
614 const char32_t shifted = codepoint - surrogate_offset;
615 result.push_back(static_cast<wchar_t>(
616 high_surrogate_base + (shifted >> surrogate_shift)));
617 result.push_back(static_cast<wchar_t>(
618 low_surrogate_base + (shifted & surrogate_mask)));
619 }
620 }
621 }
622 return result;
623 }
624
625 // Parse a .names file and index it into the appropriate map.
626 // Uses std::ifstream (byte-oriented) + utf8_to_wstring so that
627 // non-ASCII names load correctly regardless of the system locale.
628 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
629 void parse_file(const std::filesystem::path& file)
630 {
631 std::ifstream tentative_file{file};
632
633 if (tentative_file.is_open())
634 {
635 std::string raw_line;
636
637 // Read culture from the first line.
638 if (!std::getline(tentative_file, raw_line))
639 {
640 return;
641 }
642 if (!raw_line.empty() && raw_line.back() == '\r')
643 {
644 raw_line.pop_back();
645 }
646 const culture culture_read =
647 to_culture(utf8_to_wstring(raw_line));
648
649 // We can't continue without a valid culture.
650 if (culture_read == culture::any)
651 {
652 return;
653 }
654
655 // Read gender from the second line.
656 if (!std::getline(tentative_file, raw_line))
657 {
658 return;
659 }
660 if (!raw_line.empty() && raw_line.back() == '\r')
661 {
662 raw_line.pop_back();
663 }
664 const gender gender_read = to_gender(utf8_to_wstring(raw_line));
665
666 // Read all remaining lines as names.
667 name_container names_read;
668 while (std::getline(tentative_file, raw_line))
669 {
670 if (!raw_line.empty() && raw_line.back() == '\r')
671 {
672 raw_line.pop_back();
673 }
674 if (!raw_line.empty())
675 {
676 names_read.push_back(utf8_to_wstring(raw_line));
677 }
678 }
679
680 if (names_read.empty())
681 {
682 return;
683 }
684
685 // Index by gender.
686 switch (gender_read)
687 {
688 case gender::m:
689 _culture_indexed_m_names[culture_read] =
690 std::move(names_read);
691 break;
692 case gender::f:
693 _culture_indexed_f_names[culture_read] =
694 std::move(names_read);
695 break;
696 default:
697 _culture_indexed_surnames[culture_read] =
698 std::move(names_read);
699 break;
700 }
701 }
702 }
703
704 friend class name;
705 friend struct ::ng_test_access;
706};
707
708// Out-of-line definitions for name methods that call into ng.
710{
711 _owner->append_name_impl(*this, _gender, _culture);
712 return *this;
713}
714
716{
717 _owner->append_name_impl(*this, _gender, c);
718 return *this;
719}
720
722{
723 _owner->append_surname_impl(*this, _culture);
724 return *this;
725}
726
728{
729 _owner->append_surname_impl(*this, c);
730 return *this;
731}
732
733} // namespace dasmig
734
735#endif // DASMIG_NAMEGEN_HPP
Return type for name generation, holding both individual parts and the full composed string.
Definition namegen.hpp:71
friend std::wostream & operator<<(std::wostream &wos, const name &n)
Stream the name to a wide output stream.
Definition namegen.hpp:123
name & append_surname()
Append a surname to this name, preserving culture.
Definition namegen.hpp:721
name & append_name()
Append a forename to this name, preserving gender and culture.
Definition namegen.hpp:709
const std::vector< std::wstring > & parts() const
Return the individual parts (names/surnames) as a vector.
Definition namegen.hpp:83
std::uint64_t seed() const
Retrieve the random seed used to generate this name.
Definition namegen.hpp:76
Name generator that produces culture-aware names and surnames.
Definition namegen.hpp:163
name get_name(gender g=gender::any, culture c=culture::any)
Generate a first name.
Definition namegen.hpp:237
ng & operator=(const ng &)=delete
Not copyable.
ng(const ng &)=delete
Not copyable.
ng & seed(std::uint64_t seed_value)
Seed the internal random engine for deterministic sequences.
Definition namegen.hpp:295
name get_surname(culture c=culture::any)
Generate a surname.
Definition namegen.hpp:264
static gender to_gender(const std::wstring &gender_string)
Translate a gender string to a gender enum.
Definition namegen.hpp:216
bool has_resources() const
Check whether any name databases have been loaded.
Definition namegen.hpp:313
ng()=default
Default constructor — creates an empty generator with no names.
ng & unseed()
Reseed the engine with a non-deterministic source.
Definition namegen.hpp:303
void load(const std::filesystem::path &resource_path)
Load name files from a directory.
Definition namegen.hpp:326
static ng & instance()
Access the global singleton instance.
Definition namegen.hpp:181
name get_surname(culture c, std::uint64_t call_seed)
Generate a deterministic surname using a specific seed.
Definition namegen.hpp:277
name get_name(gender g, culture c, std::uint64_t call_seed)
Generate a deterministic first name using a specific seed.
Definition namegen.hpp:252
static culture to_culture(const std::wstring &country_code)
Translate an ISO 3166 2-letter country code to a culture enum.
Definition namegen.hpp:190
ng(ng &&) noexcept=default
Move constructor.
culture
Culture representing a country or a broader group.
Definition namegen.hpp:31
gender
Simple gender enum to distinguish between male and female names.
Definition namegen.hpp:59