Library / SDK
boostorg/pfr avatar
boostorg/pfr

Boost.PFR: tuple style access to aggregates, with no macro in sight

std::tuple like methods for user defined types without any macro or boilerplate code

1,493 stars174 forksC++BSL-1.0

At a glance

What is it?
Boost.PFR is a C++14 header only library that gives user defined aggregates std::tuple like access by index, a field count, and printing, with nothing declared in the struct itself. It does not depend on Boost despite living in the Boost organisation, and you can copy its include folder into a project.
Who is it for?
Boost.PFR earns its place in code that passes small aggregates around generically, where writing a getter per field for every struct is the boilerplate you keep avoiding, and the fact that it is header only with no Boost dependency makes it cheap to drop into a codebase that does not otherwise use Boost.
Can I use it commercially?
Yes. BSL-1.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 16 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

get by index on a struct that declared nothing

The motivating example is a two field aggregate and a call that looks like it should not compile:

c++
struct some_person {
  std::string name;
  unsigned birth_year;
};

some_person val{"Edgar Allan Poe", 1809};

std::cout << boost::pfr::get<0>(val)
    << " was born in " << boost::pfr::get<1>(val);

No macro appears in the struct, no friend declarations, no operator overloads, and the same call works with any aggregate type. The field order is the declaration order, so index 0 is name and index 1 is birth_year. That is the entire value proposition in one snippet: the boilerplate you would otherwise generate, or the tuple conversion you would otherwise write by hand, becomes a template call, and the struct stays a plain struct that any other code can read.

tuple_size and io cover printing and counting

Two more pieces come from the same header. tuple_size<T>::value gives the field count, and io prints a struct as a braced list. The second motivating example uses a struct with no ostream operator defined at all:

c++
struct my_struct {
    int i;
    char c;
    double d;
};

my_struct s{100, 'H', 3.141593};
std::cout << "my_struct has " << boost::pfr::tuple_size<my_struct>::value
    << " fields: " << boost::pfr::io(s) << "\n";

which prints the field count and the values as a braced list, with the double rounded to its default precision. A third example swaps the double for a std::string and shows that the output gains quotes around the string, so string members are rendered differently from arithmetic ones. That detail matters if you plan to parse the output or use it as a serial format, because io is a debug and display facility rather than a wire format.

Header only, and independent of Boost itself

The stated dependency situation is unusual for a Boost library and worth repeating: Boost.PFR is part of the Boost C++ Libraries, however it is a header only library that does not depend on Boost, and you can copy the contents of the include folder from the repository into your project and it will work. The include in the code samples is the single header form, boost/pfr.hpp, with narrower headers such as boost/pfr/io.hpp used directly where that is all you need. If the boost:: namespace itself is the objection, the README points at a separate repository, pfr_non_boost, for a version without it. Repository topics list gcc, clang, visual-studio and cpp14 through cpp17, and the top level carries the usual Boost layout with CMakeLists.txt, build.jam, modules/ and test/.

The Fusion adaptor is how it plugs into Spirit

The third motivating example is the one that shows the library's real use, because it is not about printing at all. It adapts Boost.Fusion so that a struct with no BOOST_FUSION_ADAPT_STRUCT defined can be the attribute of a Spirit X3 grammar:

c++
#include <boost/fusion/include/adapt_boost_pfr.hpp>

struct ast_employee { // No BOOST_FUSION_ADAPT_STRUCT defined
    int age;
    std::string forename;
    std::string surname;
    double salary;
};

With that one include, x3::phrase_parse fills the struct from a text format and the result prints through pfr::io. This is the pattern that saves the most code in practice. A parser front end usually owns a dozen of these structs, and the alternative to one adaptor include is one adaptation macro per struct, maintained by hand and broken whenever someone reorders a field.

Field names are promised, and the example lives in the repository

The opening paragraph lists three capabilities: access to structure elements by index, retrieval of aggregate field names, and std::tuple like methods, all without macros or boilerplate. The name retrieval is the one the motivating examples do not show, because the printed output of io is values rather than names. The repository does contain the material: example/get.cpp, example/get_name.cpp, example/sample_printing.cpp and example/quick_examples.cpp, and get_name.cpp is where the name API is demonstrated. If field names are the feature you came for, that file is the shortest path, and the developer documentation linked from the README is the reference for how names are obtained on each compiler.

Requirements and limitations are a hyperlink, not a list

The README has a section titled Requirements and Limitations and its entire content is a link to the developer documentation. That is a real gap for a library whose mechanism is compiler dependent. Boost.PFR achieves index access on aggregates by relying on what the compiler can see, and the edge cases are where such libraries differ between gcc, clang and Visual Studio. The README gives no C++ standard floor beyond calling it a C++14 library in the first line, no note about private members, base classes or non aggregate types, and no portability caveat. Read the linked documentation before committing, particularly if your build matrix includes more than one compiler or if your structs are not plain aggregates. The test results table does at least show both develop and master branches built on two CI systems with coverage tracked.

The newest tag is from 2023 while develop keeps moving

Version history is the maintenance question here. The most recent tagged releases are 2.2.0 from 2023-11-23, 2.1.0 from 2023-04-30 and 2.0.3 from 2021-10-02. The default branch is develop, not master, and the last push was on 2026-10-07. So the branch receives work that has not been cut into a release, and if you pick up the branch you are picking up unreleased code. Copying the include folder is a supported mode of use. Decide deliberately: pin 2.2.0 if you want a known version, or vendor the branch if you need whatever fix landed most recently.

Against writing the accessors or waiting for language reflection

The alternative for a handful of structs is to write the access, one getter or one visitor per type, which is boring but explicit and portable across every compiler and standard level. That cost grows with the number of types and becomes the reason libraries like this exist. The other alternative is to wait for language level reflection and stop using a workaround; for a library whose entire job is filling a gap the language has not closed, that argument is philosophical rather than practical today. Where PFR loses is exactly where a struct is not an aggregate, since the technique depends on the compiler being able to enumerate members, and where you need something more than member access, such as attributes, because it does not model metadata. Licence wise it is the Boost Software License 1.0, which is permissive and short.

Editorial conclusion

Boost.PFR earns its place in code that passes small aggregates around generically, where writing a getter per field for every struct is the boilerplate you keep avoiding, and the fact that it is header only with no Boost dependency makes it cheap to drop into a codebase that does not otherwise use Boost. Do not reach for it where the struct has private members, inherited state or a non aggregate layout, because it works on aggregates, and check the developer documentation's requirements and limitations section yourself, since the README defers to it with a link instead of listing the constraints. Before pinning a version, note that the newest tagged release is 2.2.0 from 2023-11-23 while the default branch is develop and the last push was on 2026-10-07, so a release tag and the branch are not the same code. The Boost Software License 1.0 applies, and there is a separate repository for a version without the boost:: namespace.

Frequently asked questions

Does Boost.PFR require Boost or a macro in my struct?

No. The README states it is a header only library that does not depend on Boost, and it can be used by copying the include folder into a project. No macro is required in the struct, as the third motivating example shows with a struct that has no BOOST_FUSION_ADAPT_STRUCT defined.

How do I get the number of fields in an aggregate?

Use boost::pfr::tuple_size<my_struct>::value, which the motivating examples print alongside boost::pfr::io to show the field count and the values.

Which version of Boost.PFR should I use?

The newest tagged release is 2.2.0 from 2023-11-23, while the default branch is develop and the last push was on 2026-10-07. The README also links a separate pfr_non_boost repository if you need a version without the boost:: namespace.

Official sources

  1. boostorg/pfr on GitHub
  2. License: BSL-1.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/boostorg-pfr.svg)](https://hysenlabs.com/projects/boostorg-pfr)

Was this useful?