4

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);
{
    name:sahin
    id:10
}

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

user4581301
  • 29,019
  • 5
  • 26
  • 45
Sahin
  • 137
  • 1
  • 8
  • 9
    What's your objection to using a debugger? – Bathsheba Nov 10 '20 at 22:36
  • 3
    You'll have to overload `operator< – John Bode Nov 10 '20 at 22:37
  • 4
    (Not a bad question in itself by the way - I wonder if reflection will solve this? But still use a debugger though; littering code with debugging cout statements is really intrusive.) – Bathsheba Nov 10 '20 at 22:38
  • 1
    There is a potential solution here: https://stackoverflow.com/a/17660175/7520531 – Jose Nov 10 '20 at 22:42
  • @JohnBode You can use templates to reduce the amount of overloads you have to write, but there is no single magic template that can do everything the OP wants. – G. Sliepen Nov 10 '20 at 22:45
  • 1
    This feature you want was one of the major drivers behind Rust macros as I recall it. No, there's no way to generate these functions in C++. There's no way to parse the class or struct into code, and there's no reflection. I think that some people have written things based on clang++ to generate C++ reflection structures...I haven't looked lately though. And of course IDE's like Visual Studio use their own in-built compiler / parser for Intellisense, etc... Debuggers do it by reading the generated debug info. – Zan Lynx Nov 10 '20 at 22:47
  • 1
    Maybe it's worth to have a look at https://stackoverflow.com/questions/41453/how-can-i-add-reflection-to-a-c-application . You can certainly solve your problem with metaprogramming (https://en.wikipedia.org/wiki/Metaprogramming), but that's probably overkill for your purpose. –  Nov 10 '20 at 22:52
  • 2
    Plug for my own libray. If you want to output your object as JSON then you can use ThorsSerializer: https://github.com/Loki-Astari/ThorsSerializer it would only require one extra declarations: `ThorsAnvil_MakeTrait(type, name, id);` Installable via brew on Mac/Linux – Martin York Nov 10 '20 at 22:53
  • The fundamental issue is that there is no way for a function to know the fields in any or all structures. Hmm, but what about nested structures or structures whose members are instances of structures? – Thomas Matthews Nov 10 '20 at 22:55
  • I recommend modifying your Coding Guidelines with a checkbox for overloading `operator>>` and `operator< – Thomas Matthews Nov 10 '20 at 22:57
  • 1
    Short answer: no there isn't. C++ does not work this way. – Sam Varshavchik Nov 10 '20 at 23:01
  • @SamVarshavchik C++ does work this way. I actually wrote an answer, but it's being down-voted. Even though it works :) there are some limitations and you'd need to do more work for nested structs. But it can be done. – okovko Nov 10 '20 at 23:21
  • @SamVarshavchik thanks, I got it now, with the help of the other answers – Sahin Nov 10 '20 at 23:21
  • I'm SO hyped for C++ reflection. Even though I'm in games dev and RTTI is not a consideration for releases, it will still be very useful. Long way to wait for C++23 if it even makes it? #hopeful – ワイきんぐ Nov 10 '20 at 23:29
  • C++ is supposed to be hard. If it was easy, everyone would do it. – davidbak Nov 10 '20 at 23:32
  • @ワイきんぐ C++ reflection is implemented in < 100 lines of code in the SO post that Jose linked. This type of metaprogramming has been around for a looong time. – okovko Nov 10 '20 at 23:32
  • I'd be surprised to see Java-style reflection any time soon. That's a lot of overhead that the language has mostly worked to avoid. – user4581301 Nov 10 '20 at 23:38
  • Another project to look at for ideas is nlohmann json, it includes a fairly minimalistic way to enable serialization for a struct – M.M Nov 11 '20 at 03:05

2 Answers2

5

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.

Passer By
  • 16,942
  • 5
  • 38
  • 81
  • You talk about hacks, then you count struct members using SFINAE. Really. Also "to extract the fields as usable objects without knowing their names, we may use structured bindings" This is C++17 only. You should include that in your answer. "This, unfortunately, requires a individual function for each arity." At that point you're doing more work than the friend ostream function for each struct. So you are not answering the question. By the way, the usage is not clean at all. This solution in general is wildly convoluted and full of hacks. – okovko Nov 11 '20 at 04:12
  • For a solid C++ implementation, you should refer to the Boost solution. You can find it in the SO link referenced by Jose in the comments to this question. You might also consider that my answer is like ten lines of code. Remember that OP is just trying to get some debug prints. It doesn't look like they're at the point where they would be using your library for their little assignment. Probably not even compiling with C++17. – okovko Nov 11 '20 at 04:16
  • 2
    @okovko C++17 is more or less assumed now that it's three years old and it's an untagged question. I don't think you understood how this is supposed to be used: it's a library, the last code snippet is all the user ever writes. It's literally a single function call with no modifications to anything. – Passer By Nov 11 '20 at 04:27
  • So why not generate an ostream friend function for each struct instead? You're generating way way way more than that in your library. Which your user does not want to use because it's a school assignment and they're using C++14 at best, probably C++11, maybe not even C++11 in many universities around the world. – okovko Nov 11 '20 at 04:32
-6

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.

Bathsheba
  • 220,365
  • 33
  • 331
  • 451
okovko
  • 1,485
  • 8
  • 21
  • 2
    Note that what you have in that linked example is much more complete that what's presented in this answer. You should consider migrating it and making this answer useful. – user4581301 Nov 10 '20 at 23:13
  • Rather I've made the edits. I like this answer - nothing wrong with old-fashioned methods if used appropriately, and this answer is useful in that it enunciates a technique. Have an upvote! – Bathsheba Nov 11 '20 at 08:16
  • @Bathsheba Nothing juvenile about statements of fact. I was mass down voted instantly. The first comment read that my example is not valid C++ and doesn't make any sense. – okovko Nov 11 '20 at 08:39
  • Your first example was indeed not valid C++, and very brief, such that it did not really explain to the reader how an X macro would help here. Your example doesn't define `X_my_struct` so on its own it is still not a valid piece of code. You have now provided a link to a working example, that's great and I especially like the URL :P, but you should include the whole example in the answer here on StackOverflow. URLs can go stale after all, so the answer should stand on its own. (Not all links have to be included, keeping links to Wikipedia and your PoC M4 code as they are is fine). – G. Sliepen Nov 11 '20 at 18:48
  • @G.Sliepen You still haven't clicked the Wikipedia link, have you? There's a featured section on how to print an X macro. I didn't feel the need to rewrite the classic example so I linked to it. I only bothered with an example to demonstrate that this is valid C++. – okovko Nov 14 '20 at 09:57