Created
December 22, 2025 01:02
-
-
Save neudinger/d0dcd78d415a687bffbc5bf3171cd274 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Universal C++23 Printer | |
| * * A complete tutorial implementation of std::formatter for: | |
| * - Primitives | |
| * - Enums | |
| * - Standard Containers (vector, map, set, etc.) | |
| * - Container Adapters (stack, queue) | |
| * - Tuples and Pairs | |
| * - Custom Structs (via automatic reflection emulation) | |
| * * Compile with: g++ -std=c++23 main.cpp -o universal_printer | |
| */ | |
| #include <concepts> | |
| #include <format> | |
| #include <ranges> | |
| #include <print> | |
| #include <cstdint> | |
| #include <cstdlib> | |
| #include <iostream> | |
| #include <list> | |
| #include <map> | |
| #include <memory> | |
| #include <queue> | |
| #include <set> | |
| #include <string> | |
| #include <string_view> | |
| #include <tuple> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include <array> | |
| #include <deque> | |
| #include <numbers> | |
| #include <stack> | |
| #include <typeinfo> | |
| using std::literals::operator""sv; | |
| using std::literals::operator""s; | |
| // Include ABI header for GCC/Clang to demangle type names | |
| #if defined(__GNUC__) || defined(__clang__) | |
| #include <cxxabi.h> | |
| #endif | |
| // ========================================== | |
| // 1. HELPER: Type Name Demangler | |
| // ========================================== | |
| template <typename Type> | |
| auto get_type_name() -> std::string { | |
| char const* const name = typeid(Type).name(); | |
| #if defined(__GNUC__) || defined(__clang__) | |
| int status = 0; | |
| std::unique_ptr<char, decltype(&std::free)> demangle{ | |
| abi::__cxa_demangle(name, nullptr, nullptr, &status), std::free}; | |
| if (status == 0) { | |
| return demangle.get(); | |
| } | |
| #endif | |
| return name; | |
| } | |
| // ========================================== | |
| // 2. METAPROGRAMMING: Reflection Emulation | |
| // ========================================== | |
| // A type that can be converted to anything (for detection) | |
| struct AnyType { | |
| template <class Type> | |
| operator Type() const; | |
| }; | |
| // Count fields in an aggregate struct | |
| template <class Type, std::size_t... IDX> | |
| consteval auto count_fields(std::index_sequence<IDX...> /* */) -> std::size_t { | |
| if constexpr (requires { Type{(IDX, AnyType{})..., AnyType{}}; }) { | |
| return count_fields<Type>(std::make_index_sequence<sizeof...(IDX) + 1>{}); | |
| } else { | |
| return sizeof...(IDX); | |
| } | |
| } | |
| template <typename Type> | |
| concept IsAggregate = std::is_aggregate_v<std::remove_cvref_t<Type>>; | |
| // Visitor to iterate over fields | |
| auto for_each_field(IsAggregate auto&& object, auto&& visitor) { | |
| using Type = std::remove_cvref_t<decltype(object)>; | |
| size_t constexpr field_count = count_fields<Type>(std::index_sequence<>{}); | |
| auto apply_visitor = [&](auto&&... fields) -> auto { | |
| (visitor(fields), ...); | |
| }; | |
| // Dispatch based on field count (Manual expansion required for C++23) | |
| if constexpr (field_count == 0) { | |
| return; | |
| } else if constexpr (field_count == 1) { | |
| auto&& [p1] = object; | |
| apply_visitor(p1); | |
| } else if constexpr (field_count == 2) { | |
| auto&& [p1, p2] = object; | |
| apply_visitor(p1, p2); | |
| } else if constexpr (field_count == 3) { | |
| auto&& [p1, p2, p3] = object; | |
| apply_visitor(p1, p2, p3); | |
| } else if constexpr (field_count == 4) { | |
| auto&& [p1, p2, p3, p4] = object; | |
| apply_visitor(p1, p2, p3, p4); | |
| } else if constexpr (field_count == 5) { | |
| auto&& [p1, p2, p3, p4, p5] = object; | |
| apply_visitor(p1, p2, p3, p4, p5); | |
| } else if constexpr (field_count == 6) { | |
| auto&& [p1, p2, p3, p4, p5, p6] = object; | |
| apply_visitor(p1, p2, p3, p4, p5, p6); | |
| } | |
| // Expand further if needed... | |
| } | |
| // ========================================== | |
| // 3. CONCEPTS & TRAITS | |
| // ========================================== | |
| // Detect Map-like containers (have key_type and mapped_type) | |
| template <typename T> | |
| concept IsMapContainer = requires { | |
| typename T::key_type; | |
| typename T::mapped_type; | |
| requires !std::is_same_v<typename T::key_type, typename T::mapped_type>; | |
| }; | |
| // Detect Stack or Queue (adapters with push/pop but no begin/end) | |
| template <typename Type> | |
| concept IsQueueOrStack = requires(Type container) { | |
| container.push(std::declval<typename Type::value_type>()); | |
| container.pop(); | |
| }; | |
| // Detect Standard Containers (Vector, List, etc.) excluding Maps and Adapters | |
| // We also exclude std::string/string_view to let standard formatters handle | |
| // them | |
| template <typename Type> | |
| concept HasContainer = std::ranges::range<Type>; | |
| template <typename Type> | |
| concept IsNonMapQueueContainer = | |
| HasContainer<Type> && !IsMapContainer<Type> && !IsQueueOrStack<Type> && | |
| !std::is_same_v<std::remove_cvref_t<Type>, std::string> && | |
| !std::is_same_v<std::remove_cvref_t<Type>, std::string_view> && | |
| !std::is_convertible_v<std::remove_cvref_t<Type>, std::string_view>; | |
| ; | |
| // Detect Pairs and Tuples | |
| template <typename T, template <typename...> class Primary> | |
| struct is_specialization_of : std::false_type {}; | |
| template <template <typename...> class Primary, typename... Args> | |
| struct is_specialization_of<Primary<Args...>, Primary> : std::true_type {}; | |
| template <typename T, template <typename...> class Primary> | |
| inline constexpr bool is_specialization_of_v = | |
| is_specialization_of<T, Primary>::value; | |
| template <typename T> | |
| concept TupleOrPair = | |
| is_specialization_of_v<std::remove_cvref_t<T>, std::tuple> || | |
| is_specialization_of_v<std::remove_cvref_t<T>, std::pair>; | |
| // Detect Simple Structs for Reflection | |
| // Must be aggregate, not a range, and not a string | |
| template <typename Type> | |
| concept IsSimpleStruct = | |
| std::is_aggregate_v<Type> && !std::ranges::range<Type> && | |
| !std::is_same_v<Type, std::string> && | |
| !TupleOrPair<Type>; // Avoid conflict with tuples | |
| // ========================================== | |
| // 4. USER TYPES (Enums & Structs) | |
| // ========================================== | |
| enum class UserDataType : uint8_t { | |
| UNSPECIFIED = 0, | |
| HUMAN = 1, | |
| API = 2, | |
| SERVER = 3, | |
| SERVICE = 4, | |
| }; | |
| // Specialization for Enum | |
| template <> | |
| struct std::formatter<UserDataType> : std::formatter<std::string> { | |
| auto format(UserDataType const& user_type, auto& ctx) const noexcept { | |
| switch (static_cast<uint8_t>(user_type)) { | |
| case static_cast<uint8_t>(UserDataType::UNSPECIFIED): | |
| return std::format_to(ctx.out(), "UNSPECIFIED"); | |
| case static_cast<uint8_t>(UserDataType::HUMAN): | |
| return std::format_to(ctx.out(), "HUMAN"); | |
| case static_cast<uint8_t>(UserDataType::API): | |
| return std::format_to(ctx.out(), "API"); | |
| case static_cast<uint8_t>(UserDataType::SERVER): | |
| return std::format_to(ctx.out(), "SERVER"); | |
| case static_cast<uint8_t>(UserDataType::SERVICE): | |
| return std::format_to(ctx.out(), "SERVICE"); | |
| default: | |
| return std::format_to(ctx.out(), "UNKNOWN"); | |
| } | |
| } | |
| }; | |
| struct UserData { | |
| int64_t user_id; | |
| std::string email; | |
| std::string name; | |
| UserDataType type; | |
| std::vector<std::string> prefered_colors; | |
| bool is_active; | |
| }; | |
| // Forward declaration for the Explicit Formatter | |
| template <> | |
| struct std::formatter<UserData>; | |
| // ========================================== | |
| // 5. GENERIC FORMATTERS | |
| // ========================================== | |
| // 5.1 Pairs and Tuples | |
| template <TupleOrPair Type> | |
| struct std::formatter<Type> : std::formatter<std::string> { | |
| auto format(Type const& obj, auto& ctx) const noexcept { | |
| std::format_to(ctx.out(), "{{ "); | |
| std::apply( | |
| [&](auto&&... args) { | |
| std::size_t index = 0; | |
| auto out = ctx.out(); | |
| ((out = std::format_to(out, "{}", args), | |
| (++index < sizeof...(args) ? out = std::format_to(out, ", ") | |
| : out)), | |
| ...); | |
| }, | |
| obj); | |
| return std::format_to(ctx.out(), " }}"); | |
| } | |
| }; | |
| // 5.2 Maps | |
| template <IsMapContainer MapType> | |
| struct std::formatter<MapType> : std::formatter<std::string> { | |
| auto format(auto const& datas, auto& ctx) const noexcept { | |
| auto const distance = std::size(datas); | |
| using KeyType = typename MapType::key_type; | |
| using ValType = typename MapType::mapped_type; | |
| std::format_to(ctx.out(), "(size:'{}', type:'{}, {}') : [", distance, | |
| get_type_name<KeyType>(), get_type_name<ValType>()); | |
| if (distance != 0) { | |
| for (auto const& [key, value] : datas) { | |
| std::format_to(ctx.out(), ", {{ {}, {} }} ", key, value); | |
| } | |
| } | |
| return std::format_to(ctx.out(), " ]"); | |
| } | |
| }; | |
| // 5.3 Stacks and Queues (Container Adapters) | |
| template <IsQueueOrStack QueueStack> | |
| struct std::formatter<QueueStack> : std::formatter<std::string> { | |
| auto format(auto const& datas, auto& ctx) const noexcept { | |
| auto const distance = std::size(datas); | |
| using SubType = typename QueueStack::value_type; | |
| auto const* const sub_type_name = typeid(SubType).name(); | |
| // Copy to traverse | |
| QueueStack copy_tmp(datas); | |
| auto get_current = [](auto const& container) -> auto const& { | |
| if constexpr (requires { container.top(); }) { | |
| return container.top(); | |
| } else { | |
| return container.front(); | |
| } | |
| }; | |
| std::format_to(ctx.out(), "(size:'{}', type:'{}') : [", distance, | |
| sub_type_name); | |
| if (distance != 0) { | |
| for (size_t i = 0; i < distance - 1; ++i) { | |
| std::format_to(ctx.out(), " {},", get_current(copy_tmp)); | |
| copy_tmp.pop(); | |
| } | |
| std::format_to(ctx.out(), " {} ]", get_current(copy_tmp)); | |
| } else { | |
| std::format_to(ctx.out(), "]"); | |
| } | |
| return ctx.out(); | |
| } | |
| }; | |
| // 5.4 Standard Iterable Containers (Vector, Set, List, etc.) | |
| template <IsNonMapQueueContainer Range> | |
| struct std::formatter<Range> : std::formatter<std::string> { | |
| auto format(auto const& datas, auto& ctx) const noexcept { | |
| auto const distance = std::size(datas); | |
| // Safe extraction of value type | |
| using ValueType = std::ranges::range_value_t<Range>; | |
| auto const type_name = get_type_name<ValueType>(); | |
| std::format_to(ctx.out(), "(size:'{}', type:'{}') : [", distance, | |
| type_name); | |
| if (distance != 0) { | |
| for (auto const& item : std::ranges::views::take(datas, distance - 1)) { | |
| std::format_to(ctx.out(), " {},", item); | |
| } | |
| auto last = datas | std::ranges::views::drop(distance - 1); | |
| std::format_to(ctx.out(), " {} ]", last.front()); | |
| } else { | |
| std::format_to(ctx.out(), "]"); | |
| } | |
| return ctx.out(); | |
| } | |
| }; | |
| // 5.5 Automatic Struct Reflection | |
| // Note: This must be less specialized than the others to avoid conflicts. | |
| template <IsSimpleStruct Type> | |
| struct std::formatter<Type> : std::formatter<std::string> { | |
| auto format(Type const& obj, auto& ctx) const noexcept { | |
| std::format_to(ctx.out(), "Struct {{ "); | |
| for_each_field(obj, [&](auto const& field) -> auto { | |
| std::format_to(ctx.out(), " {} ", field); | |
| }); | |
| return std::format_to(ctx.out(), " }}"); | |
| } | |
| }; | |
| // 5.6 Explicit Specialization for UserData | |
| // (demonstrating how to override the generic struct printer) | |
| template <> | |
| struct std::formatter<UserData> : std::formatter<std::string> { | |
| auto format(UserData const& user_data, auto& ctx) const noexcept { | |
| return std::format_to( | |
| ctx.out(), | |
| R"(UserData(id="{}", email="{}", name="{}", type="{}", colors="{}", is_active="{}"))", | |
| user_data.user_id, user_data.email, user_data.name, user_data.type, | |
| user_data.prefered_colors, user_data.is_active); | |
| } | |
| }; | |
| // ========================================== | |
| // 6. MAIN EXECUTION | |
| // ========================================== | |
| // A simple struct to test automatic reflection | |
| struct Parameter { | |
| std::string name; | |
| std::string value; | |
| std::string description; | |
| }; | |
| // g++ -std=c++23 full-serial.cpp -o full-serial | |
| // ./full-serial | |
| auto main() -> int { | |
| std::println("=== C++23 Universal Printer Demo ===\n"); | |
| // 1. Primitive Types | |
| std::println("--- 1. Primitives ---"); | |
| std::println("Integer: {}", std::numeric_limits<int32_t>::max()); | |
| std::println("Float: {}", std::numbers::sqrt2_v<float>); | |
| std::println("Double: {}", std::numbers::pi); | |
| std::println("Long Double: {}", std::numeric_limits<long double>::max()); | |
| std::println("Bool: {} or {}", true, false); | |
| std::println("String: {}", "Hello World"s); | |
| std::println("String View: {}", "Hello World View"sv); | |
| std::println("C String: {}", "Hello World C String"); | |
| // 2. Custom Enum | |
| std::println("\n--- 2. Custom Enums ---"); | |
| std::println("UserType: {}", UserDataType::API); | |
| std::println("UserType: {}", UserDataType::SERVER); | |
| // 3. Automatic Struct Reflection | |
| std::println("\n--- 3. Reflection (Simple Structs) ---"); | |
| Parameter const param{.name = "ConfigOption", | |
| .value = "true", | |
| .description = "Enables the feature"}; | |
| std::println("Parameter: {}", param); | |
| // 4. Complex Struct (Explicit Formatter) | |
| std::println("\n--- 4. Complex Struct (Explicit Formatter) ---"); | |
| UserData const user_data{.user_id = 101, | |
| .email = "alice@example.com", | |
| .name = "Alice", | |
| .type = UserDataType::HUMAN, | |
| .prefered_colors = {"#FF0000", "#00FF00"}, | |
| .is_active = true}; | |
| std::println("User: {}", user_data); | |
| // 5. Standard Containers | |
| std::println("\n--- 5. Standard Containers ---"); | |
| std::vector<int> vec{1, 2, 3, 4, 5}; | |
| std::println("Vector<int>: {}", vec); | |
| std::vector<char> chars{'a', 'b', 'c'}; | |
| std::println("Vector<char>: {}", chars); | |
| std::list<double> list_vals{1.1, 2.2, 3.3}; | |
| std::println("List<double>: {}", list_vals); | |
| std::set<float> set_vals{1.1f, 2.2f, 3.3f}; | |
| std::println("Set<float>: {}", set_vals); | |
| std::deque<int> deque_vals{10, 20, 30}; | |
| std::println("Deque<int>: {}", deque_vals); | |
| std::array<std::string, 2> arr{"Start", "End"}; | |
| std::println("Array<string>: {}", arr); | |
| // 6. Maps (Key-Value) | |
| std::println("\n--- 6. Maps ---"); | |
| std::map<int, std::string> map_vals{{1, "One"}, {2, "Two"}}; | |
| std::println("Map<int, str>: {}", map_vals); | |
| // 7. Container Adapters (Stack/Queue) | |
| std::println("\n--- 7. Adapters (Stack/Queue) ---"); | |
| std::queue<float> queue_vals; | |
| queue_vals.push(10.5f); | |
| queue_vals.push(20.5f); | |
| std::println("Queue<float>: {}", queue_vals); | |
| std::stack<int> stack_vals; | |
| stack_vals.push(100); | |
| stack_vals.push(200); | |
| std::println("Stack<int>: {}", stack_vals); | |
| // 8. Heterogeneous Types (Tuple/Pair) | |
| std::println("\n--- 8. Tuples & Pairs ---"); | |
| std::pair<int, double> p{1, 99.9}; | |
| std::println("Pair: {}", p); | |
| std::tuple<int, std::string, double> t{1, "Data", 3.14}; | |
| std::println("Tuple: {}", t); | |
| // 9. Composition (Vectors of Structs) | |
| std::println("\n--- 9. Composition (Vector of Users) ---"); | |
| std::vector<UserData> users; | |
| users.reserve(2); | |
| users.push_back(user_data); | |
| users.push_back( | |
| {102, "bot@example.com", "Bot", UserDataType::API, {"#000000"}}); | |
| std::println("Database: {}", users); | |
| return EXIT_SUCCESS; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment