MCPcopy Create free account
hub / github.com/cbeck88/visit_struct

github.com/cbeck88/visit_struct @1.2.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release 1.2.0 ↗ · + Follow
193 symbols 269 edges 10 files 9 documented · 5% updated 8mo ago1.2.0 · 2025-07-24★ 65213 open issues

Browse by type

Functions 110 Types & classes 83
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

visit_struct

Build Status

Boost licensed

A header-only library providing structure visitors for C++11 and C++14.

Motivation

In C++ there is no built-in way to iterate over the members of a struct type.

Oftentimes, an application may contain several small "POD" datatypes, and one would like to be able to easily serialize and deserialize, print them in debugging info, and so on. Usually, the programmer has to write a bunch of boilerplate for each one of these, listing the struct members over and over again.

(This is only the most obvious use of structure visitors.)

Naively one would like to be able to write something like:

for (const auto & member : my_struct) {
  std::cerr << member.name << ": " << member.value << std::endl;
}

However, this syntax can never be legal in C++, because when we iterate using a for loop, the iterator has a fixed static type, and member.value similarly has a fixed static type. But the struct member types must be allowed to vary.

Visitors

The usual way to overcome issues like that (without taking a performance hit) is to use the visitor pattern. For our purposes, a visitor is a generic callable object. Suppose our struct looks like this:

struct my_type {
  int a;
  float b;
  std::string c;
};

and suppose we had a function like this, which calls the visitor v once for each member of the struct:

template <typename V>
void visit(const my_type & my_struct, V && v) {
  v("a", my_struct.a);
  v("b", my_struct.b);
  v("c", my_struct.c);
}

(For comparison, see also the function boost::apply_visitor from the boost::variant library, which similarly applies a visitor to the value stored within a variant.)

Then we can "simulate" the for-loop that we wanted to write in a variety of ways. For instance, we can make a template function out of the body of the for-loop and use that as a visitor.

template <typename T>
void log_func(const char * name, const T & value) {
  std::cerr << name << ": " << value << std::endl;
}

visit(my_struct, log_func);

Using a template function here means that even though a struct may contain several different types, the compiler figures out which function to call at compile-time, and we don't do any run-time polymorphism -- the whole call can often be inlined.

Basically we are solving the original problem in a very exact way -- there is no longer an explicit iterator, and each time the "loop body" can be instantiated with different types as needed.

If the loop has internal state or "output", we can use a function object (an object which overloads operator()) as the visitor, and collect the state in its members. Also in C++14 we have generic lambdas, which sometimes makes all this very terse.

Additionally, while making a visitor is sometimes more verbose than you'd like, it has an added benefit that generic visitors can be used and reused many times. Often, when doing things like logging or serialization, you don't want each struct to get a different implementation or policy, you want to reuse the same code for all of them.

Reflection

So, if we have a template function visit for our struct, it may let us simplify code and promote code reuse.

However, that means we still have to actually define visit for every struct we want to use it with, and possibly several versions of it, taking const my_type &, my_type &, my_type &&, and so on. That's also quite a bit of repetitive code, and the whole point of this is to reduce repetition.

Again, ideally we would be able to do something totally generic, like,

template <typename S, typename V>
void for_each(S && s, V && v) {
  // Insert magic here...
  for (auto && member : s) {
    v(member.name, member.value);
  }
}

where both the visitor and struct are template parameters, and use this to visit the members of any struct.

Unfortunately, current versions of C++ lack reflection. It's not possible to programmatically inspect the list of members of a generic class type S, using templates or anything else standard, even if S is a complete type (in which case, the compiler obviously knows its members). If we're lucky we might get something like this in C++20, but right now there's no way to actually implement the fully generic for_each.

This means that any implementation of for_each requires some help, usually in the form of registration macros or similar.

Overview

This library permits the following syntax in a C++11 program:

struct my_type {
  int a;
  float b;
  std::string c;
};

VISITABLE_STRUCT(my_type, a, b, c);



struct debug_printer {
  template <typename T>
  void operator()(const char * name, const T & value) {
    std::cerr << name << ": " << value << std::endl;
  }
};

void debug_print(const my_type & my_struct) {
  visit_struct::for_each(my_struct, debug_printer{});
}

Intuitively, you can think that the macro VISITABLE_STRUCT is defining overloads of visit_struct::for_each for your structure.

In C++14 this can be made more succinct using a lambda:

void debug_print(const my_type & my_struct) {
  visit_struct::for_each(my_struct,
    [](const char * name, const auto & value) {
      std::cerr << name << ": " << value << std::endl;
    });
}

These two things, the macro VISITABLE_STRUCT and the function visit_struct::for_each, represent the most important functionality of the library.

A nice feature of visit_struct is that for_each always respects the C++11 value category of it's arguments. That is, if my_struct is a const l-value reference, non-const l-value reference, or r-value reference, then for_each will pass each of the fields to the visitor correspondingly, and the visitor is also forwarded properly.

It should be noted that there are already libraries that permit iterating over a structure like this, such as boost::fusion, which does this and much more. Or boost::hana, which is like a modern successor to boost::fusion which takes advantage of C++14.

However, our library can be used as a single-header, header-only library with no external dependencies. The core visit_struct.hpp is in total about four hundred lines of code, depending on how you count, and is fully functional on its own. For some applications, visit_struct is all that you need.

Additionally, the syntax for doing these kind of visitations is (IMO) a little nicer than in fusion or hana. And visit_struct has much better compiler support right now than hana. hana requires a high level of conformance to C++14. It only supports gcc-6 and up for instance, and doesn't work with any versions of MSVC. (Its support on clang is quite good.) visit_struct can be used with many "first generation C++11 compilers" that are now quite old, like gcc-4.8 and MSVC 2013.

Note: The macro VISITABLE_STRUCT must be used at filescope, an error will occur if it is used within a namespace. You can simply include the namespaces as part of the type, e.g.

VISITABLE_STRUCT(foo::bar::baz, a, b, c);

Compatibility with boost::fusion

visit_struct also has support code so that it can be used with "fusion-adapted structures". That is, any structure that boost::fusion knows about, can also be used with visit_struct::for_each, if you include the extra header.

#include <visit_struct/visit_struct_boost_fusion.hpp>

This compatability header means that you don't have to register a struct once with fusion and once with visit_struct. It may help if you are migrating from one library to the other.

Compatiblity with boost::hana

visit_struct also has a similar compatibility header for boost::hana.

#include <visit_struct/visit_struct_boost_hana.hpp>

"Intrusive" Syntax

A drawback of the basic syntax is that you have to repeat the field member names.

This introduces a maintenance burden: What if someone adds a field member and doesn't update the list?

  1. It is possible to write a static assertion that all of the members are registered, by comparing sizeof the struct with what it should be given the known registered members. (See test_fully_visitable.cpp )
  2. It may be useful to register only a subset of the field members for serialization.
  3. It may be a requirement for you that you cannot change the header where the struct is defined, and you still want to visit it, so the first syntax may be pretty much the only option for you.

However, none of these changes the fact that with the first syntax, you have to write the names twice.

If visit_struct were e.g. a clang plugin instead of a header-only library, then perhaps we could make the syntax look like this:

struct my_type {
  __attribute__("visitable") int a;
  __attribute__("visitable") float b;
  __attribute__("visitable") std::string c;
};

void debug_print(const my_type & my_struct) {
  __builtin_visit_struct(my_struct,
    [](const char * name, const auto & member) {
      std::cout << name << ": " << member << std::endl;
    });
}

We don't offer a clang plugin like this, but we do offer an additional header, visit_struct_intrusive.hpp which uses macros to get pretty close to this syntax, and which is portable:

struct my_type {
  BEGIN_VISITABLES(my_type);
  VISITABLE(int, a);
  VISITABLE(float, b);
  VISITABLE(std::string, c);
  END_VISITABLES;
};

This declares a structure which is essentially the same as

struct my_type {
  int a;
  float b;
  std::string c;
};

There are no additional data members defined within the type, although there are some "secret" static declarations which are occurring. (Basically, a bunch of typedef's.) That's why it's "intrusive". There is still no run-time overhead.

Each line above expands to a separate series of declarations within the body of my_type, and arbitrary other C++ declarations may appear between them.

struct my_type {

  int not_visitable;
  double not_visitable_either;

  BEGIN_VISITABLES(my_type);
  VISITABLE(int, a);
  VISITABLE(float, b);

  typedef std::pair<std::string, std::string> spair;

  VISITABLE(spair, p);

  void do_nothing() const { }

  VISITABLE(std::string, c);

  END_VISITABLES;
};

When visit_struct::for_each is used, each member declared with VISITABLE will be visited, in the order that they are declared.

The benefits of this version are that, you don't need to type all the member names twice, and you don't need to jump out of your namespaces back to filescope in order to register a struct. The main drawbacks are that this is still somewhat verbose, the implementation is a bit more complicated, and this one may not be useful in some cases, like if the struct you want to visit belongs to some other project and you can't change its definition.

Binary Vistation

visit_struct also supports visiting two instances of the same struct type at once.

For instance, the function call

visit_struct::for_each(s1, s2, v);

is similar to

v("a", s1.a, s2.a);
v("b", s1.b, s2.b);
v("c", s1.c, s2.c);

This is useful for implementing generic equality and comparison operators for visitable structures, for instance. Here's an example of a generic function struct_eq which compares any two visitable structures for equality using operator == on each field, and which short-circuits properly.

struct eq_visitor {
  bool result = true;

  template <typename T>
  void operator()(const char *, const T & t1, const T & t2) {
    result = result && (t1 == t2);
  }
};

template <typename T>
bool struct_eq(const T & t1, const T & t2) {
  eq_visitor vis;
  visit_struct::for_each(t1, t2, vis);
  return vis.result;
}

On clang 3.5 with a simple example, this compiles the same assembly as a hand-rolled equality operator. See it on [godbolt compiler explorer](https://gcc.godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAKxAEZSBnVAV2OUxAHIB6bgaj4QAwgEo%2BQ1AAcAnsTzAEBPgCYADLQCsfALQr1AdnEI5DIpISZifAEKZkAawCkqgILOXvAQBE8puQCNmAkx0PmYAOywrAgsbVFRTPgBlVAAzAgB3AENiTD4AGTx2cIZMUj4ANUsGPFRwvloAOlVGwSTMPKzkNABbSSzw6Txw4HdPPlS8ABs8/IBJIWwAOSTsPhI%2BNBk%2BLKVFAkkQXgyTxv9400aSYG55xZXsAH1aR5aCAA8CEXcf5QBmPCpSKYVKVOZJOYAFUeSUhACUAKpCaEACQACmjHnMlkJ8givNgvL8/lhJuE8hVwVCYfCkaiMVicXiCUTXGMAFSc9x8dl8NHEVAANzwWAYOwmXWmhGkfCIfCwyCmOU6fD8zGQBFYnTFjmUymFNQIWX8M11yh2kR2kkkUxlWT4BsIJG5vLlhFakNiU18SjSfB6mB6/mqfF84t6kmmmG0BDwAflu3tao1WvKA1CMUseTD4VQLr4xAiMbjeSFlgsWXQjRd3B%2BbP%2Bw0VzCwfEcfyEQSlBGkbewxMbU2beTbQm7kkwjwIxCyhAYvbrHn4ADENgBZJIVIR6Wh/VXMa0kAjlDJ5SRBTZ1UyYd6SKzBhDDUL20nDTDVtn13eA4GgykQ6GwoiyKPEIADyKyQtgAAaaJwsSAihqCEAvuSoSPOuQiPBU2BwmIuoAGwEYI6FJJh2Fwq27Ztl4DQAByqKo3yfghAgoRSVIAbSwFgRB0GwfBAiYFMpQCQhbFgv%2BNJAdCPGwnxFFoCUwQ3sQomYJEgLwep%2BCpAunguOkljiLq1gmbQAAssqxFk1q2o8jpEFYqQRBqtT1MgAx8MGF5Kdet7voufAAOp5EaxDAJgShCCZZn0F554MOqCATC5saXjsuSylkwARaEf7UoBdIgVBUEWSB4FyTBcIBfB36khJBVcTJJVlbJkFVaJgKCMhIKvmhGFYTheHKIRI3EQN5GUdFfw0bQACcDEiHh%2BjRStghgGAbFoY8yA2swDB7Qwy2rVuECPDth2HZRRIzdutCqH8%2BRMW4/wsfKvXkg1nHScVpXmeVvEdcxgnCZgomsR97GSYV3Etf9bXyT5V4qWpGm6cx2maR%2BLjhFkAYHV0eQOY8yZKI4%2BimdjuP4/07CytOs6thTen8PycY5DKwR9EqwR8BkD7IMlYYHXYeBZN6ABeISyqgBaYMAPpGfaY5g64XM2rsw7tir1N5JC5Q63jeTLC41j5Gs1EOqgIrzq4pMOr4hDGqafyU24rieCiQnjtEgYa7z/NFMlgt2PYYpdcr0jjqGYq5ArV65Og7jqzzWujlH6lG3w%2Buyhnut8CbZsW7dgrW0nfx9nbU7qkovj2Y7RomnkICqgQ6AgCAqTi6Uk4Z0zpn6DdbvJ37qdTYb8aQrbLj23XDnO2D7b69yb2r2vufjvnpjtyAmdN48mnawzBAMB389NyOU8V2f4vMIv2Adyr098C32%2BP4WE4q/35NDyzfBe1MH2spR6az5gLZKuQehljFMgQUFpQi5FSFmcIdNUgCh6OKL%2BEAGBZEFJgMUTB4xjmGMAZ6KdQEjgnnrae9tFSYE8uTN2AgVb1Soa3HekCyw7UFJQvOWc34gE4Xgx4iDkHsEvr2R%2BGdJEgC/k/V2PxB5tmHmrEBvNeGbyzlfSuLh9okM2DMAYk5rob0zvGOhAwJHX1kdIhR2NPCrmGHgbQeDiA1DqOsUEAjehQPCL3aOEATzrHCLaOWABHZgeBMrRWUKZWJtBaBkLUWnNhOc2EIhodXDUF4ei%2BP8cOZmrhmEZ3qgqKYKsID

Core symbols most depended-on inside this repo

Shape

Class 83
Method 81
Function 29

Languages

C++97%
Python3%

Modules by API surface

include/visit_struct/visit_struct_intrusive.hpp31 symbols
include/visit_struct/visit_struct.hpp31 symbols
test_visit_struct.cpp24 symbols
include/visit_struct/visit_struct_boost_fusion.hpp22 symbols
test_visit_struct_boost_hana.cpp20 symbols
test_visit_struct_intrusive.cpp19 symbols
test_visit_struct_boost_fusion.cpp17 symbols
test_fully_visitable.cpp16 symbols
include/visit_struct/visit_struct_boost_hana.hpp8 symbols
generate_pp_map.py5 symbols

For agents

$ claude mcp add visit_struct \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page