Skip to content

Instantly share code, notes, and snippets.

@neudinger
Created December 22, 2025 01:02
Show Gist options
  • Select an option

  • Save neudinger/d0dcd78d415a687bffbc5bf3171cd274 to your computer and use it in GitHub Desktop.

Select an option

Save neudinger/d0dcd78d415a687bffbc5bf3171cd274 to your computer and use it in GitHub Desktop.
/**
* 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