Easy way to cout any struct in c++

Viewed 3119

In c++, is there a way to define a function that prints every struct? I mean, in our project, there are lots of structs; I need to print them sometimes, to debug. I want them to be printed easily, without adding friend ostream function to the every struct.

struct type{
    string name;
    int id;
}
type aType;
aType.name= "sahin";
aType.id=10;

The function can work like this:

printStruct(aType);

output:
{
    name:sahin
    id:10
}

And then, if possible, I need a way to print vectors of structs. Similar to JSON.stringify in js.

3 Answers

Generally, you will need to have static reflection to iterate through the fields and print them, which C++ doesn't have.

Unless you hack the language really hard.

First, you "scan" the struct for field count

struct ubiq
{
    template <typename T>
    operator T();
};

template <size_t>
using ubiq_t = ubiq;

template <typename T, typename... Ubiqs>
constexpr auto count_r(size_t& sz, int) -> std::void_t<decltype(T{Ubiqs{}...})>
{
    sz = sizeof...(Ubiqs);
}

template <typename T, typename, typename... Ubiqs>
constexpr auto count_r(size_t& sz, float)
{
    count_r<T, Ubiqs...>(sz, 0);
}

template<typename T, size_t... Is>
constexpr auto count(std::index_sequence<Is...>)
{
    size_t sz;
    count_r<T, ubiq_t<Is>...>(sz, 0);
    return sz;
}

template<typename T>
constexpr auto count()
{
    return count<T>(std::make_index_sequence<sizeof(T)>{});
}

Which works by attempting to initialize the struct with ubiqs, a type which has a fake conversion operator to anything. Combined with SFINAE, you can check how many of them is needed, which will be the field count.

Then, to extract the fields as usable objects without knowing their names, we may use structured bindings. For example, for two fields we can

template <typename T>
inline auto as_tuple(T&& t, std::integral_constant<size_t, 2>)
{
    auto& [x0, x1] = t;
    return std::forward_as_tuple(x0, x1);
}

This, unfortunately, requires a individual function for each arity. But suppose we have all the arity we need, then we could get and use the fields as

struct S { int x; std::string s; };
S s = {42, "42"};
auto tup = as_tuple(s, std::integral_constant<size_t, count<S>()>{});
std::cout << get<0>(tup) << ' ' << get<1>(tup);

Now, all you have to do is to glue everything together to make it automagically print any aggregate type.

template<typename T>
void println(T&& t)
{
    using rT = std::remove_reference_t<T>;
    auto tup = as_tuple(t, std::integral_constant<size_t, count<rT>()>{});
    std::apply([](auto&... xs) { ((std::cout << xs << ' '), ...); }, tup);
}

It so happens that I already wrote a library ezprint which does it for you (and some more). The end result you get is

ez::println(s);  // prints {42 42}

Without defining anything for the custom type.

Boost.PFR known eralier as "magic get" also does it without any additional helper macros.

The approach looks similar to the one from @Passer By answer.

X macros.

From the Wikipedia link "It [usage of X macros] remains useful also in modern-day C and C++" and "we can generate a function that prints the variables [of a struct]".

#define X(type, name) /*deref or destructure*/name <<
std::cout << X_my_struct std::endl;
#undef X

You can figure out some way to improve the notation. The last X should just be "name" not "name <<", so you can write X_my_struct << std::endl which would be preferred.

Here is a simplified working example. You'll need to do a little more work to get it how you like it.

If you are interested in writing X macros for the general case, this can be done, but I didn't include it for simplicity. Let me know if you're interested. For example, you sometimes want X(type, name) and other times X(type, name, value) for the struct definition. You'd use X(...) and the N_ARG trick to handle this.

A user pointed out this requires rewriting your structs / classes. I don't believe that is entirely accurate. I wrote a proof of concept program in M4 to generate X macros from C header files. This can be done as part of the compilation stage to generate a header file for X macros of all structs of interest.

Related