Skip to content

Inspect

The inspect API provides deep, structured inspection tools for C++ values.

It is designed for debugging, diagnostics, type exploration, container analysis, and development-time visibility.

cpp
#include <vix/inspect.hpp>

int main()
{
  vix::inspect(42);
  vix::inspect(std::vector<int>{1, 2, 3});
  vix::inspect_type<std::vector<int>>();

  return 0;
}
cpp
#include <vix/inspect.hpp>

Some projects may expose the API through a broader Vix include:

cpp
#include <vix.hpp>

For direct usage, prefer:

cpp
#include <vix/inspect.hpp>

What inspect does

vix::inspect renders a value with debugging-oriented information.

Compared to vix::print, inspect is more diagnostic.

Use print when you want simple output:

cpp
vix::print("user", 42);

Use inspect when you want to understand a value, a type, a container, a struct, or a runtime state:

cpp
vix::inspect(std::vector<int>{1, 2, 3});
vix::inspect_type<std::vector<int>>();
vix::inspect_container(std::vector<int>{1, 2, 3});

Basic usage

cpp
#include <vix/inspect.hpp>
#include <vector>
#include <string>

int main()
{
  vix::inspect(42);
  vix::inspect(true);
  vix::inspect(std::string{"hello"});
  vix::inspect(std::vector<int>{1, 2, 3});

  return 0;
}

Output shape:

txt
42
true
"hello" [len=5]
[1, 2, 3] [n=3]

The exact output can vary depending on options such as show_type, compact, and max_items.

Inspect vs print

APIPurpose
vix::printSimple readable output.
vix::inspectDebugging-oriented value inspection.
vix::inspect_typeType metadata inspection.
vix::inspect_metaValue plus type metadata.
vix::inspect_treeTree view for nested structures.
vix::inspect_bytesRaw byte dump for trivially copyable values.
vix::inspect_numericNumeric range statistics.

Use inspect while debugging.

Use print for simple developer output.

Use log for production logs.

Public API overview

APIPurpose
vix::inspect(value)Inspect a value to stdout.
vix::inspect(value, opts)Inspect a value with custom options.
vix::inspect_to(os, value)Inspect a value to a specific stream.
vix::inspect_to_string(value)Return inspection output as a string.
vix::inspect_type<T>()Inspect type metadata for T.
vix::inspect_type(value)Inspect the deduced type of a value.
vix::inspect_line(...)Inspect multiple values on one line.
vix::inspect_value(label, value)Inspect a named value.
vix::inspect_all(...)Inspect multiple values, one per line.
vix::inspect_meta(value)Inspect value and full type metadata.
vix::inspect_compact(value)Return compact inspection string.
vix::inspect_verbose(value)Inspect with type tags, metadata, and addresses.
vix::inspect_report(obj)Detailed report for field_map structs.
vix::inspect_container(c)Container size, capacity, value type, elements.
vix::inspect_bytes(value)Hex dump of raw object bytes.
vix::inspect_diff(a, b)Compare two values side by side.
vix::inspect_check(label, expected, actual)Print PASS or FAIL check.
vix::inspect_tap(value)Inspect and return the same value.
vix::inspect_if(condition, value)Inspect only when condition is true.
vix::tap_with(value, fn)Run a callback and return the same value.
vix::inspect_numeric(range)Numeric range statistics and histogram.
vix::inspect_tree(value)Visual tree representation.
vix::inspect_path<T>()Return the selected inspection path.
vix::inspect_paths<Ts...>()Print inspection paths for many types.

inspect_options

inspect_options controls how inspection is rendered.

cpp
vix::inspect_options opts;
opts.show_type = true;
opts.max_depth = 4;
opts.max_items = 10;

vix::inspect(std::vector<int>{1, 2, 3}, opts);

Fields

FieldDefaultPurpose
max_depth8Maximum recursive nesting depth.
max_items64Maximum items per container.
show_typetrueShow type annotations when supported.
show_metafalseEnable full metadata mode for verbose use.
compactfalseUse compact single-line output.
show_addressfalseShow or follow object addresses when supported.
indent_str" "Indentation unit.
out&std::coutOutput stream.

Default options

default_options() returns the thread-local default inspection options.

cpp
auto &opts = vix::default_options();

opts.show_type = true;
opts.max_items = 20;
opts.max_depth = 5;

vix::inspect(std::vector<int>{1, 2, 3});

Because the default options are thread-local, changing them affects the current thread.

Scoped options

Use scoped_inspect_options to temporarily override options.

cpp
vix::inspect(std::vector<int>{1, 2, 3});

{
  vix::scoped_inspect_options guard{};
  guard.opts.compact = true;
  guard.opts.show_type = false;

  vix::inspect(std::vector<int>{1, 2, 3});
}

vix::inspect(std::vector<int>{1, 2, 3});

When the guard is destroyed, the previous options are restored.

Inspect a value

cpp
vix::inspect(42);
vix::inspect("hello");
vix::inspect(std::vector<int>{1, 2, 3});

Inspect with custom options

cpp
vix::inspect_options opts;
opts.show_type = false;
opts.compact = true;
opts.max_items = 3;

vix::inspect(std::vector<int>{1, 2, 3, 4, 5}, opts);

Output shape:

txt
[1, 2, 3, ...]

Inspect to a stream

cpp
std::ostringstream out;

vix::inspect_to(out, std::vector<int>{1, 2, 3});

std::string result = out.str();

Inspect to string

cpp
std::string s = vix::inspect_to_string(std::vector<int>{1, 2, 3});

vix::print(s);

Compact inspection

inspect_compact returns a compact string.

cpp
auto s = vix::inspect_compact(
  std::map<std::string, int>{{"a", 1}, {"b", 2}}
);

vix::print(s);

Output shape:

txt
{a: 1, b: 2}

Inspect several values on one line

cpp
int a = 1;
int b = 2;
int c = 3;

vix::inspect_line(a, b, c);

Output shape:

txt
1 | 2 | 3

Inspect named values

cpp
int batch_size = 64;

vix::inspect_value("batch_size", batch_size);

Output:

txt
batch_size: 64

Inspect multiple values

cpp
vix::inspect_all(
  std::string{"hello"},
  42,
  3.14,
  std::vector<int>{1, 2, 3}
);

Each value is inspected on its own line.

Supported types

inspect supports the same broad type categories as the Vix rendering engine, with extra diagnostic information.

Type categoryExamples
Booleansbool
Characterschar, wchar_t, char8_t, char16_t, char32_t
Stringsstd::string, std::string_view, const char *
Wide stringsstd::wstring, std::wstring_view, const wchar_t *
Numbersint, double, std::size_t, etc.
Nullnullptr
Enumsenum, enum class
Filesystemstd::filesystem::path
Optionalstd::optional<T>
Variantstd::variant<Ts...>
Anystd::any
Reference wrapperstd::reference_wrapper<T>
Chrono durationstd::chrono::seconds, milliseconds, etc.
Chrono time pointstd::chrono::time_point
Smart pointersstd::unique_ptr, std::shared_ptr, std::weak_ptr
Raw pointersT *
Function pointersfunction pointer values
Rangesstd::vector, std::list, std::deque, etc.
Setsstd::set, std::unordered_set, etc.
Mapsstd::map, std::unordered_map, etc.
Container adaptersstd::stack, std::queue, std::priority_queue
Tuple-like valuesstd::tuple, std::pair, std::array
Error typesstd::error_code, std::error_condition
Bytestd::byte
Monostatestd::monostate
Streamable typesAny type with operator<<
Custom inspected typesvix::inspector<T>, vix_inspect, or field_map<T>

If C++23 std::expected is available, std::expected<T, E> is also supported.

Strings

inspect shows strings with quotes and length information when type display is enabled.

cpp
vix::inspect(std::string{"hello"});

Output shape:

txt
"hello" [len=5]

Booleans

cpp
vix::inspect(true);
vix::inspect(false);

Output:

txt
true
false

Characters

cpp
vix::inspect('A');

Output:

txt
'A'

Unsigned character values may be shown as hexadecimal.

Null values

cpp
vix::inspect(nullptr);

Output:

txt
nullptr

Vectors and sequences

cpp
vix::inspect(std::vector<int>{1, 2, 3});

Output shape:

txt
[1, 2, 3] [n=3]

Maps

cpp
std::map<std::string, int> values = {
  {"users", 120},
  {"orders", 42}
};

vix::inspect(values);

Output shape:

txt
{"orders": 42, "users": 120} [n=2]

The exact ordering depends on the container.

Optionals

cpp
std::optional<int> a = 42;
std::optional<int> b;

vix::inspect(a);
vix::inspect(b);

Output:

txt
Some(42)
None<int>

Variants

cpp
std::variant<int, std::string> value = std::string{"active"};

vix::inspect(value);

Output shape:

txt
variant<index=1>("active" [len=6])

Any

cpp
std::any a = 42;
std::any empty;

vix::inspect(a);
vix::inspect(empty);

Output shape:

txt
any(int)
any(empty)

std::any shows the stored type name, not the stored value.

Filesystem paths

cpp
std::filesystem::path path = "src/main.cpp";

vix::inspect(path);

Output shape:

txt
path("src/main.cpp") [exists=true]

The exists value depends on the filesystem.

Chrono durations

cpp
using namespace std::chrono;

vix::inspect(42ns);
vix::inspect(100us);
vix::inspect(1500ms);
vix::inspect(60s);
vix::inspect(2min);
vix::inspect(1h);

Output shape:

txt
42ns
100µs
1500ms
60s
2min
1h

Chrono time points

cpp
auto now = std::chrono::system_clock::now();

vix::inspect(now);

Output shape:

txt
<time_point: 1710000000000ms from epoch>

Smart pointers

cpp
auto value = std::make_unique<int>(42);
std::unique_ptr<int> empty;

vix::inspect(value);
vix::inspect(empty);

Output:

txt
unique_ptr(42)
unique_ptr(null)

Shared pointers show the use count:

cpp
auto shared = std::make_shared<std::string>("hello");

vix::inspect(shared);

Output shape:

txt
shared_ptr[use_count=1]("hello" [len=5])

Weak pointers show whether they are expired or lockable.

cpp
std::weak_ptr<std::string> weak = shared;

vix::inspect(weak);

Output shape:

txt
weak_ptr[use_count=2]("hello" [len=5])

Raw pointers

cpp
int value = 42;
int *ptr = &value;

vix::inspect(ptr);

Output shape:

txt
<ptr:0x...>

By default, raw pointers show their address.

To inspect the pointed-to value, enable show_address:

cpp
vix::inspect_options opts;
opts.show_address = true;

vix::inspect(ptr, opts);

Output shape:

txt
<ptr:0x...> -> 42

Function pointers

cpp
void (*fn)(int) = [](int) {};

vix::inspect(fn);

Output shape:

txt
<fptr:0x...>

Container adapters

Container adapters are copied before inspection, so the original container is not modified.

Stack

cpp
std::stack<int> s;
s.push(1);
s.push(2);
s.push(3);

vix::inspect(s);

Output shape:

txt
stack[1, 2, 3]

Queue

cpp
std::queue<std::string> q;
q.push("first");
q.push("second");

vix::inspect(q);

Output shape:

txt
queue["first" [len=5], "second" [len=6]]

Priority queue

cpp
std::priority_queue<int> pq;
pq.push(5);
pq.push(1);
pq.push(4);

vix::inspect(pq);

Output shape:

txt
priority_queue[5, 4, 1]

Type metadata

inspect_type<T>() prints a detailed report about a type.

cpp
vix::inspect_type<int>();
vix::inspect_type<std::vector<int>>();

Output shape:

txt
type_info {
  name: vector<int>
  full_name: std::vector<int, std::allocator<int> >
  size: 24 bytes
  align: 8 bytes
  categories: [class]
  traits: [...]
}

Inspect the type of a value

cpp
std::vector<int> values = {1, 2, 3};

vix::inspect_type(values);

This deduces the type from the value.

Type metadata structure

Internally, Vix builds a type_metadata object.

It includes:

Field groupExamples
Type namesname, full_name
Memory layoutsize_bytes, align_bytes
Categoriesis_integral, is_class, is_enum, is_pointer, etc.
Qualifiersis_const, is_volatile
Class traitsis_aggregate, is_standard_layout, is_trivially_copyable, etc.
Construction traitsis_default_constructible, is_copy_constructible, etc.
Sign traitsis_signed, is_unsigned

You can build metadata manually:

cpp
auto meta = vix::make_type_metadata<std::vector<int>>();

Inspect value plus metadata

Use inspect_meta to show both the value and the type report.

cpp
std::vector<int> values = {1, 2, 3};

vix::inspect_meta(values);

Output shape:

txt
value: [1, 2, 3] [n=3]
type_info {
  name: vector<int>
  size: 24 bytes
  align: 8 bytes
  categories: [class]
  traits: [...]
}

Verbose inspection

inspect_verbose enables type tags, metadata mode, and address display.

cpp
int value = 42;
int *ptr = &value;

vix::inspect_verbose(ptr);

Use it when normal inspection is not enough.

Inspect containers

inspect_container prints container-specific metadata.

cpp
std::vector<int> values = {1, 2, 3};

vix::inspect_container(values);

Output shape:

txt
vector<int> {
  size:    3
  capacity: 3
  max_size: ...
  empty:    false
  value_type: int
  value_size: 4 bytes
  elements: [1, 2, 3]
}

For unordered containers, it can also show bucket information when available.

Inspect raw bytes

inspect_bytes prints a hexadecimal byte dump.

It only works for trivially copyable types.

cpp
int value = 42;

vix::inspect_bytes(value, "answer");

Output shape:

txt
answer - int [4 bytes @ 0x...]:
  2A 00 00 00

The byte order depends on the machine architecture.

Inspect two values

Use inspect_diff to compare two values side by side.

cpp
vix::inspect_diff(42, 43, "expected", "actual");

Output:

txt
expected: 42
actual: 43
types_equal: true
values_equal: false

It prints values_equal only when operator== is available.

Inspect checks

inspect_check prints a PASS or FAIL result.

cpp
vix::inspect_check("2 + 2 == 4", 4, 2 + 2);
vix::inspect_check("1 + 1 == 3", 3, 1 + 1);

Output shape:

txt
[PASS] 2 + 2 == 4
[FAIL] 1 + 1 == 3
  expected: 3
  actual:   2

It also returns a bool.

cpp
bool ok = vix::inspect_check("sum", 10, 5 + 5);

Inspect tap

inspect_tap inspects a value and returns it unchanged.

cpp
int value = vix::inspect_tap(2 + 3, "sum");

vix::print(value);

Output shape:

txt
sum: 5
5

Use this to inspect intermediate values without breaking expression flow.

Conditional inspect

cpp
int value = 42;

vix::inspect_if(value > 10, value, "value");

This only prints when the condition is true.

Tap with custom function

cpp
auto value = vix::tap_with(42, [](const auto &x) {
  vix::inspect_value("intermediate", x);
});

tap_with calls the function, then returns the original value.

Inspect numeric ranges

inspect_numeric prints statistics for arithmetic ranges.

cpp
std::vector<int> scores = {10, 20, 30, 40, 50};

vix::inspect_numeric(scores, "scores");

Output shape:

txt
scores:
  count:    5
  min:      10
  max:      50
  sum:      150
  mean:     30
  stddev:   14.1421
  histogram:
    [...]

For small or simple ranges, the histogram may not be printed.

Inspect a tree

inspect_tree prints nested structures as a visual tree.

cpp
std::map<std::string, std::vector<int>> data = {
  {"a", {1, 2, 3}},
  {"b", {4, 5, 6}}
};

vix::inspect_tree(data, "data");

Output shape:

txt
└─ data [2 items]
   ├─ a [3 items]
   │  ├─ [0]: 1
   │  ├─ [1]: 2
   │  └─ [2]: 3
   └─ b [3 items]
      ├─ [0]: 4
      ├─ [1]: 5
      └─ [2]: 6

Use inspect_tree for nested maps, vectors, optionals, variants, and structs registered with field_map.

Custom type support

There are three main ways to teach inspect about custom types.

  1. Define operator<<
  2. Define an ADL vix_inspect hook
  3. Specialize vix::inspector<T>
  4. Register fields with vix::field_map<T>

field_map is the most structured option.

Method 1: use operator<<

cpp
#include <ostream>
#include <string>
#include <vix/inspect.hpp>

struct User
{
  int id{};
  std::string name;
};

std::ostream &operator<<(std::ostream &os, const User &user)
{
  return os << "User{id=" << user.id << ", name=" << user.name << "}";
}

int main()
{
  User user{1, "Ada"};

  vix::inspect(user);

  return 0;
}

Use this when stream output is useful outside Vix too.

Method 2: use ADL vix_inspect

Define vix_inspect in the same namespace as the type.

cpp
#include <vix/inspect.hpp>

namespace app
{
  struct Vec2
  {
    float x{};
    float y{};
  };

  void vix_inspect(vix::inspect_context &ctx, const Vec2 &v)
  {
    ctx.os << "Vec2{x=" << v.x << ", y=" << v.y << "}";
  }
}

int main()
{
  app::Vec2 v{1.5f, 2.5f};

  vix::inspect(v);

  return 0;
}

Use this when you want Vix-specific inspection without specializing templates.

Method 3: specialize vix::inspector<T>

cpp
#include <string>
#include <vix/inspect.hpp>

struct NamedResult
{
  std::string label;
  double value{};
};

template <>
struct vix::inspector<NamedResult>
{
  static void inspect(vix::inspect_context &ctx, const NamedResult &result)
  {
    ctx.os << result.label << '=' << result.value;

    if (ctx.opts.show_type)
      ctx.os << " [NamedResult]";
  }
};

int main()
{
  vix::inspect(NamedResult{"loss", 0.42});

  return 0;
}

Use this when you want full control over inspection.

Method 4: register fields with field_map

field_map gives Vix a lightweight reflection-like way to inspect struct fields.

cpp
#include <string>
#include <vector>
#include <optional>
#include <vix/inspect.hpp>

struct OptimizerConfig
{
  std::string name;
  int batch_size{};
  double learning_rate{};
  bool use_cache{};
  std::vector<int> layer_sizes;
  std::optional<double> dropout;
};

template <>
struct vix::field_map<OptimizerConfig>
{
  static constexpr auto fields()
  {
    return vix::fields(
      vix::field("name", &OptimizerConfig::name),
      vix::field("batch_size", &OptimizerConfig::batch_size),
      vix::field("learning_rate", &OptimizerConfig::learning_rate),
      vix::field("use_cache", &OptimizerConfig::use_cache),
      vix::field("layer_sizes", &OptimizerConfig::layer_sizes),
      vix::field("dropout", &OptimizerConfig::dropout)
    );
  }
};

int main()
{
  OptimizerConfig cfg{
    "adam",
    64,
    0.001,
    true,
    {128, 64, 32},
    0.1
  };

  vix::inspect(cfg);
  vix::inspect_report(cfg);
  vix::inspect_tree(cfg, "config");

  return 0;
}

Output shape:

txt
OptimizerConfig {
  name: "adam" [len=4],
  batch_size: 64,
  learning_rate: 0.001,
  use_cache: true,
  layer_sizes: [128, 64, 32] [n=3],
  dropout: Some(0.1)
}

Field descriptors

field creates a field descriptor:

cpp
vix::field("name", &User::name)

fields groups descriptors into a tuple:

cpp
return vix::fields(
  vix::field("id", &User::id),
  vix::field("name", &User::name)
);

This is used by field_map<T>::fields().

Inspect reports for structs

inspect_report works for types that have a field_map.

cpp
vix::inspect_report(cfg);

Output shape:

txt
══ Inspect Report: OptimizerConfig ══
  sizeof:  ...
  alignof: ...
  layout:  standard
  trivial: no
  fields:
    name: "adam" [len=4]
    batch_size: 64
    learning_rate: 0.001
    use_cache: true
    layer_sizes: [128, 64, 32] [n=3]
    dropout: Some(0.1)
══════════════════════════════

Inspection priority

When Vix inspects a value, it chooses the first matching path.

Priority:

  1. vix::inspector<T> specialization
  2. ADL vix_inspect
  3. field_map<T>
  4. nullptr_t
  5. bool
  6. character types
  7. wide strings
  8. string-like types
  9. std::filesystem::path
  10. std::any
  11. std::optional
  12. std::variant
  13. std::reference_wrapper
  14. chrono duration
  15. chrono time point
  16. std::unique_ptr
  17. std::shared_ptr
  18. std::weak_ptr
  19. container adapters
  20. std::expected, when available
  21. enum
  22. map-like range
  23. sequence range
  24. pair
  25. tuple-like
  26. raw pointer
  27. streamable via operator<<
  28. fallback uninspectable type

Inspect the selected path

Use inspect_path<T>() to see which path will be used.

cpp
vix::print(vix::inspect_path<int>());
vix::print(vix::inspect_path<std::vector<int>>());
vix::print(vix::inspect_path<std::map<std::string, int>>());

Output shape:

txt
bool
range
map-like range

Print many paths:

cpp
vix::inspect_paths<
  int,
  std::string,
  std::vector<int>,
  std::map<std::string, int>,
  std::optional<int>
>();

Demangled type names

The inspect API includes helpers under vix::demangle.

cpp
std::string full = vix::demangle::type_name<std::vector<int>>();
std::string short_name = vix::demangle::short_type_name<std::vector<int>>();

Useful helpers:

APIPurpose
vix::demangle::type_name<T>()Full human-readable type name when supported.
vix::demangle::type_name_of(value)Type name of a deduced value.
vix::demangle::shorten(name)Remove common namespace noise.
vix::demangle::short_type_name<T>()Shortened type name.

On GCC and Clang, Vix uses ABI demangling when available. On other compilers, it falls back to typeid().name().

Complete example

cpp
#include <map>
#include <optional>
#include <string>
#include <variant>
#include <vector>
#include <vix/inspect.hpp>
#include <vix/print.hpp>

struct User
{
  int id{};
  std::string name;
  std::vector<std::string> roles;
  std::optional<double> score;
};

template <>
struct vix::field_map<User>
{
  static constexpr auto fields()
  {
    return vix::fields(
      vix::field("id", &User::id),
      vix::field("name", &User::name),
      vix::field("roles", &User::roles),
      vix::field("score", &User::score)
    );
  }
};

int main()
{
  User user{
    1,
    "Ada",
    {"admin", "developer"},
    98.5
  };

  vix::inspect(user);
  vix::inspect_report(user);
  vix::inspect_tree(user, "user");
  vix::inspect_type<User>();
  vix::inspect_meta(user);

  std::vector<int> values = {10, 20, 30, 40, 50};
  vix::inspect_container(values);
  vix::inspect_numeric(values, "values");

  vix::inspect_check("user id", 1, user.id);

  std::string compact = vix::inspect_compact(user);
  vix::print_named("compact", compact);

  return 0;
}

Use in a Vix app

cpp
#include <vix.hpp>
#include <vix/inspect.hpp>

using namespace vix;

int main()
{
  App app;

  app.get("/debug", [](Request &req, Response &res) {
    vix::inspect_value("path", req.path());
    vix::inspect_value("query", req.query());

    res.json({
      "ok", true,
      "message", "debug inspected"
    });
  });

  app.run(8080);

  return 0;
}

Use this during local development.

For production logs, prefer:

cpp
#include <vix/log.hpp>

vix::log::info("debug route called");

Common mistakes

Forgetting the header

cpp
// Wrong
vix::inspect(value);

Fix:

cpp
#include <vix/inspect.hpp>

Printing instead of inspecting

Use print for simple output:

cpp
vix::print("hello", 42);

Use inspect for debugging values:

cpp
vix::inspect(std::vector<int>{1, 2, 3});

Inspecting huge containers

cpp
std::vector<int> values(1'000'000);

vix::inspect(values);

Better:

cpp
vix::inspect_options opts;
opts.max_items = 20;

vix::inspect(values, opts);

Recursing too deeply

For nested structures, set max_depth.

cpp
vix::inspect_options opts;
opts.max_depth = 3;

vix::inspect(data, opts);

Expecting std::any to show the stored value

cpp
std::any value = 42;

vix::inspect(value);

Output shape:

txt
any(int)

std::any shows the stored type name, not the stored value.

Expecting raw pointers to dereference by default

cpp
int value = 42;
int *ptr = &value;

vix::inspect(ptr);

Output shape:

txt
<ptr:0x...>

Enable show_address if you want pointer dereference inspection when possible.

Using inspect_bytes on non-trivially-copyable types

inspect_bytes requires a trivially copyable type.

Good:

cpp
int x = 42;
vix::inspect_bytes(x);

Not good:

cpp
std::string s = "hello";
// vix::inspect_bytes(s); // not intended

Using inspect as production logging

This is useful during development:

cpp
vix::inspect(user);

For production logs, use:

cpp
vix::log::info("user loaded");

Best practices

Use inspect while developing:

cpp
vix::inspect(value);

Use inspect_value for named values:

cpp
vix::inspect_value("config", config);

Use inspect_type when learning or debugging template types:

cpp
vix::inspect_type<decltype(value)>();

Use field_map for important structs:

cpp
template <>
struct vix::field_map<MyStruct>
{
  static constexpr auto fields()
  {
    return vix::fields(
      vix::field("id", &MyStruct::id),
      vix::field("name", &MyStruct::name)
    );
  }
};

Use inspect_report for structured objects.

Use inspect_tree for nested data.

Use inspect_numeric for numeric ranges.

Use inspect_check for lightweight debug checks.

API reference

inspect

cpp
template <typename T>
void inspect(const T &value);

Inspects a value to the default output stream.

cpp
vix::inspect(42);

inspect with options

cpp
template <typename T>
void inspect(const T &value, const inspect_options &opts);

Inspects a value with explicit options.

cpp
vix::inspect_options opts;
opts.compact = true;

vix::inspect(value, opts);

inspect_to

cpp
template <typename T>
void inspect_to(std::ostream &os, const T &value);

Inspects a value to a specific stream.

cpp
std::ostringstream out;
vix::inspect_to(out, value);

inspect_to_string

cpp
template <typename T>
std::string inspect_to_string(const T &value);

template <typename T>
std::string inspect_to_string(const T &value, const inspect_options &opts);

Returns inspection output as a string.

cpp
std::string s = vix::inspect_to_string(value);

inspect_type

cpp
template <typename T>
void inspect_type();

template <typename T>
void inspect_type(const T &value);

Prints type metadata.

cpp
vix::inspect_type<std::vector<int>>();

std::vector<int> values;
vix::inspect_type(values);

inspect_line

cpp
template <typename... Args>
void inspect_line(const Args &...args);

Inspects multiple values on a single line separated by |.

cpp
vix::inspect_line(1, 2, 3);

inspect_value

cpp
template <typename T>
void inspect_value(std::string_view label, const T &value);

Inspects a named value.

cpp
vix::inspect_value("port", 8080);

inspect_all

cpp
template <typename... Args>
void inspect_all(const Args &...args);

Inspects several values, each on its own line.

cpp
vix::inspect_all("hello", 42, std::vector<int>{1, 2});

inspect_meta

cpp
template <typename T>
void inspect_meta(const T &value);

Prints the value and its full type metadata.

cpp
vix::inspect_meta(value);

inspect_compact

cpp
template <typename T>
std::string inspect_compact(const T &value);

Returns a compact inspection string.

cpp
std::string s = vix::inspect_compact(value);

inspect_verbose

cpp
template <typename T>
void inspect_verbose(const T &value);

Inspects with verbose settings.

cpp
vix::inspect_verbose(value);

inspect_report

cpp
template <typename T>
void inspect_report(const T &obj);

Requires field_map<T>.

Prints a detailed report for a struct.

cpp
vix::inspect_report(config);

inspect_container

cpp
template <typename T>
void inspect_container(const T &c);

Requires a range.

Prints container metadata and elements.

cpp
vix::inspect_container(std::vector<int>{1, 2, 3});

inspect_bytes

cpp
template <typename T>
void inspect_bytes(const T &value, std::string_view label = "");

Requires a trivially copyable type.

Prints a byte dump.

cpp
int value = 42;
vix::inspect_bytes(value, "value");

inspect_diff

cpp
template <typename A, typename B>
void inspect_diff(
  const A &a,
  const B &b,
  std::string_view label_a = "a",
  std::string_view label_b = "b"
);

Compares two values side by side.

cpp
vix::inspect_diff(42, 43, "expected", "actual");

inspect_check

cpp
template <typename Expected, typename Actual>
bool inspect_check(
  std::string_view label,
  const Expected &expected,
  const Actual &actual
);

Prints PASS or FAIL and returns bool.

cpp
bool ok = vix::inspect_check("sum", 4, 2 + 2);

inspect_tap

cpp
template <typename T>
const T &inspect_tap(const T &value, std::string_view label = "");

Inspects and returns the original value.

cpp
auto result = vix::inspect_tap(compute(), "result");

inspect_if

cpp
template <typename T>
void inspect_if(bool condition, const T &value, std::string_view label = "");

Inspects only if the condition is true.

cpp
vix::inspect_if(debug_enabled, value, "value");

tap_with

cpp
template <typename T, typename Fn>
const T &tap_with(const T &value, Fn &&fn);

Runs a callback and returns the original value.

cpp
auto value = vix::tap_with(compute(), [](const auto &x) {
  vix::inspect_value("x", x);
});

inspect_numeric

cpp
template <typename Range>
void inspect_numeric(const Range &rng, std::string_view label = "");

Requires a range of arithmetic values.

Prints numeric statistics.

cpp
vix::inspect_numeric(std::vector<int>{1, 2, 3}, "values");

inspect_tree

cpp
template <typename T>
void inspect_tree(const T &value, std::string_view root_label = "root");

Prints a tree representation.

cpp
vix::inspect_tree(data, "data");

inspect_path

cpp
template <typename T>
std::string_view inspect_path() noexcept;

Returns the selected inspection strategy for a type.

cpp
auto path = vix::inspect_path<std::vector<int>>();

inspect_paths

cpp
template <typename... Ts>
void inspect_paths();

Prints inspection strategies for several types.

cpp
vix::inspect_paths<int, std::string, std::vector<int>>();

make_type_metadata

cpp
template <typename T>
type_metadata make_type_metadata();

Builds type metadata for T.

cpp
auto meta = vix::make_type_metadata<MyType>();

field

cpp
template <typename Owner, typename FieldT>
constexpr auto field(std::string_view name, FieldT Owner::*ptr);

Creates a field descriptor.

cpp
vix::field("id", &User::id)

fields

cpp
template <typename... Fields>
constexpr auto fields(Fields &&...fs);

Groups field descriptors.

cpp
return vix::fields(
  vix::field("id", &User::id),
  vix::field("name", &User::name)
);

field_map

cpp
template <typename T, typename = void>
struct field_map;

Specialize this template to register fields for a custom struct.

cpp
template <>
struct vix::field_map<User>
{
  static constexpr auto fields()
  {
    return vix::fields(
      vix::field("id", &User::id),
      vix::field("name", &User::name)
    );
  }
};

inspector

cpp
template <typename T, typename = void>
struct inspector;

Specialize this template to customize inspection.

cpp
template <>
struct vix::inspector<MyType>
{
  static void inspect(vix::inspect_context &ctx, const MyType &value)
  {
    ctx.os << "...";
  }
};

streamable_inspector

cpp
template <typename T>
struct streamable_inspector;

Helper for inspector specializations that delegate to operator<<.

cpp
template <>
struct vix::inspector<MyType> : vix::streamable_inspector<MyType>
{
};

scoped_inspect_options

cpp
class scoped_inspect_options;

Temporarily overrides default inspect options and restores them automatically.

cpp
{
  vix::scoped_inspect_options guard{};
  guard.opts.compact = true;
  vix::inspect(value);
}

Summary

vix::inspect is the debugging and diagnostics API for Vix.

Use it when you need to understand:

  • values,
  • types,
  • containers,
  • structs,
  • nested data,
  • memory bytes,
  • numeric ranges,
  • differences between values,
  • selected rendering paths.

Core tools:

cpp
vix::inspect(value);
vix::inspect_value("name", value);
vix::inspect_type<T>();
vix::inspect_meta(value);
vix::inspect_report(obj);
vix::inspect_container(container);
vix::inspect_bytes(value);
vix::inspect_numeric(values);
vix::inspect_tree(data);
vix::inspect_check("label", expected, actual);

For simple output, use vix::print.

For production logs, use vix::log.

For deep debugging, use vix::inspect.

Released under the MIT License.