2

I'm trying to write a pretty-printer for a class containing a std::set of objects for which I'm also supplying my own pretty printer. Very basically, this is how my C++ code looks like:

#include <set>
#include <iostream>
#include <cassert>

class Foo {
public:
  int x;

  bool operator<(const Foo & rhs) const {
    return this->x < rhs.x;
  }
};

class FooContainer {
public:
  std::set<Foo> content;
};

int main(int argc, char **argv) {
  FooContainer c;
  Foo f1 {1};
  Foo f2 {2};
  c.content.insert(f1);
  c.content.insert(f2);

  assert(false); // hand over to gdb
}

I want to be able to pretty-print objects of class "FooContainer". So, I want pretty-printers that look somehow like these:

class FooPrinter(object):
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "X: " + str(self.val['x'])

class FooContainerPrinter(object):
    def __init__(self, val):
        self.val = val

    def to_string(self):
        res = ""
        for foo in self.val['content']:
            res += " " + FooPrinter(foo).to_string()
        return res

However, trying these, GDB gives me an error:

(gdb) p c 
Python Exception <class 'TypeError'> 'gdb.Value' object is not iterable: 
$7 =

It looks like the FooContainerPrinter only has access to the internal members of a std::set, and can't iterate it. I would really like to avoid having to traverse the red-black-tree behind that std::set myself. Is there a neat trick to achieve this?

Lukas Barth
  • 2,734
  • 18
  • 43

2 Answers2

2

There's no good way to do exactly what you want. The main issue is that the pretty-printing API was purposely kept simple (arguably a bit too simple) and so it doesn't provide a programmable way to pick apart containers -- it only provides just what is needed for printing, which is sometimes less general.

However, in this situation one viable approach might be to defer to the std::set printer.

That is, simply drop the FooContainer printer, and just write a Foo printer. A FooContainer will be printed using the default gdb style, the enclosed std::set will be displayed using the libstdc++ printer, and the individual elements will be displayed using your Foo printer.

If you really want to print the entire contents as one long string, though, then I'm afraid you'll have to dig up the std::set printer and extract some code from it.

Tom Tromey
  • 21,507
  • 2
  • 45
  • 63
  • Regarding Tom's last point, see the `RbtreeIterator` type (which turns GCC's RB trees into Python iterables over all the nodes in the tree) and the `get_value_from_Rb_tree_node` function which does what its name implies. You probably don't need to extract code from them, just reuse them. I hope. – Jonathan Wakely Apr 20 '17 at 16:52
  • That's a good idea, with the caveat that in the future it's possible your code would have to adapt if there are changes in libstdc++. I had forgotten that this code used a convenient helper class... – Tom Tromey Apr 20 '17 at 18:05
  • Thanks for the answers. However, I think I found a way of doing what I want by hijacking the default pretty printer. See my answer for details. – Lukas Barth Apr 24 '17 at 14:07
1

After some trying, I've found a way that comes very close. I'm basically using the default StdSetPrinter provided with the stdlib, but I'm not using it for printing, just for iterating the set. My code looks like this now:

from libstdcxx.v6.printers import StdSetPrinter

class FooPrinter(object):
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "X: " + str(self.val['x'])

class FooContainerPrinter(object):
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "My Foo Container"

    def children(self):
        pp = StdSetPrinter("dummy", self.val['content'])
        return pp.children()

Now, the default pretty printing magic still adds some boilerplate (basically it outputs "My Foo Container = { … &langle; pretty-print of the contents &rangle; …}") but that is fine with me. I think it would even be able to not define an own children(), but rather use the pp.children() inside to_string() and thus have full control over the output string.

It has the drawback that the path where libstdc++ puts its default pretty printers needs to be in the PYTHONPATH.

Lukas Barth
  • 2,734
  • 18
  • 43