
OK, here's my review of the property tree library, actually quite a difficult review to write given all the hyperbole that's gone before. Still that's better than not having anyone interested ! :-) First of all I do not believe that the potential uses of this library are so similar to the serialisation or program options library that it should be excluded on those grounds. Secondly the library looks mature and usable enough that I would consider using it to manage program configuration. However, I do have problems with the conceptual scope of the library as described in the docs, more on this later but it's not the generic tree that the docs give the impression of it being on first view. I also have problems with the interface: that it's needlessly idiosyncratic in places. Not so much as to render it unusable at all, but I believe better can be done. OK down to nut's and bolts, first off the interface: 1) As others have commented, there are too many member functions IMO. I don't believe that changing to non-members offers any benefit in this case (and may actually be harmful due to ADL). Let's start with child access, I can't believe you're not using operator[] here. Yes I know it's cute, but it does work well for std::map. How about: mymap b; b[a][b][c] = 2; // assigns value 2 to node at a/b/c The way I'm figuring this would work is: * Accessing a child node with operator[] always succeeds but may point to a "ghost" node that doesn't actually exist. * Assignment to a node always succeeds, the node gets created if required. * Reading from a node throws if the node doesn't exist yet, or if the conversion can't be performed. * If you read a Boost::optional then it shouldn't throw, and no need for a special member function. I'm assuming that operator [] returns another "property map" by value BTW. You could have a single function that returns the node from a path of some kind as well, but *please* make the separator parameter the second argument with a default parameter. BTW, you can do much the same thing with the / operator (as has already been suggested), boost::filesystem makes this work rather well, and I guess I'd be just as happy with: property-map m = mymap / "he" / "she" / "it"; or int val = (mymap / "he" / "she" / "it").get<int>(); And finally if a property-tree is usable in boolean contexts you can detect whether a node actually exists already, or is a "ghost" node with nothing in it: tree-type a; // read in some data.... tree-type b = a["ha"]["ha"]; if(b) { // do something... } Reading data I'm inclined to suggest an unreasonably "cute" interface here: just provide a template conversion operator so that: int i = mymap["fee"]["far"]["thumb"]; does what you would expect and throws if the node/value doesn't exist or the conversion to int can't be done. boost::optional opt = mymap["fee"]["far"]["thumb"]; would never throw, and int i = mymap["fee"]["far"]["thumb"].get(3); returns 3 if there is no data. A step to far? Maybe, and I would be perfectly happy with a single get() function that did all the getting, again not throwing if the type being fetched is a boost::optional, and with a single argument overload version for data with default values. Setting data: You must know what's coming... what's wrong with operator = ? OK you may need a member function to deal with std::locale, but you know what, I bet 99.999% of programmers would be completely happy with just the default (or the locale could be a property of the property-tree as a whole - just add one imbue() member function and use it's locale everywhere). Of course if the argument to operator= is another property tree, then it copies the tree to that node rather than setting the data. The comments so far relate to any kind of hierarchical "tree like" associative storage, but now for the real guts of the issue for me (and I think why this library has attracted so much comment). What is this library trying to be? Yes, I've seen you're posts, and I believe I do understand what it's for :-) But that's not the point, the docs and actually the interface are at odds with what it's for. There is definitely a place in boost for a generic tree container. But this isn't one as you have said many times I believe, but to look at the interface it's kind of pretending to be one :-) So what is this library? I believe it's an interface to a hierarchical database, in fact I believe it need not be an in memory container at all. I certainly wouldn't try and read (and then write back!!!) the entire windows registry, or any large part of it, with this library. And yet, why shouldn't it be able to provide a view onto large data sets that form some kind of hierarchy? I'm thinking out loud here, but what would happen if your read/write functions were replaced by some kind of opaque data access API, that the tree used internally to access data from it's actual source. Possibly this is a step too far, but it may be food for thought anyway. I guess what I'm saying is either lets have: * A generic tree data structure with serialisation to various formats (not necessarily using boost.serialisation depending on practicalities). * A wrapper around external hierarchical databases, that doesn't necessarily load data into memory. Conclusions: I believe we need a library like this in Boost. And I believe this one is - in spite of what I've said above - perfectly usable. However, I believe a better interface is possible. So, I'm going to vote weakly "no": for the review manager that means this is not an "over-my-dead-body" vote, it's a "can-do-better on the interface, but let it through if there's a consensus for it". Whew. Regards, John Maddock.

John Maddock wrote:
Let's start with child access, I can't believe you're not using operator[] here. Yes I know it's cute, but it does work well for std::map.
How about:
mymap b; b[a][b][c] = 2; // assigns value 2 to node at a/b/c
The way I'm figuring this would work is:
* Accessing a child node with operator[] always succeeds but may point to a "ghost" node that doesn't actually exist. * Assignment to a node always succeeds, the node gets created if required. * Reading from a node throws if the node doesn't exist yet, or if the conversion can't be performed. * If you read a Boost::optional then it shouldn't throw, and no need for a special member function.
I'm assuming that operator [] returns another "property map" by value BTW.
Seems both expensive (unless state is shared using intrisive_ptr), and impossible, since the returned instance would not know how to change the original instance. Are you sure you don't mean "by reference", perhaps overloaded on const-ness? -Thorsten

John Maddock wrote:
boost::optional opt = mymap["fee"]["far"]["thumb"];
would never throw, and
In fact, it wouldn't even compile, because optional is a template. But I get what you mean, and I like it. boost::optional<int> opt = mymap["fee"]["far"]; throws if fee/far exists and is not convertible to int, returns none if fee/far does not exist, returns the converted value if it exists and is convertible. Sebastian Redl

John Maddock wrote:
... How about:
mymap b; b[a][b][c] = 2; // assigns value 2 to node at a/b/c
The way I'm figuring this would work is:
* Accessing a child node with operator[] always succeeds but may point to a "ghost" node that doesn't actually exist. * Assignment to a node always succeeds, the node gets created if required. ... int i = mymap["fee"]["far"]["thumb"].get(3);
returns 3 if there is no data.
Just my 2 cents, this is just how it's implemented it in jsoncpp (which doesn't even attempt to do half of what the proposed library do): http://jsoncpp.sourceforge.net/ http://jsoncpp.sourceforge.net/class_json_1_1_value.html To my knowledge JDOM and TinyXML also provides a similar mecanism. So you may have put your finger on something, Baptiste.

Hi John, Thank you for the time you took to review the library. Although your vote is no, you presented an interesting alternative to current syntax, and also to other proposed syntaxes. I was unaware of the potential that operator [] had in the context of ptree when coupled with overloaded, templated conversion operator and "ghost nodes". Originally I dismissed it, because it was limited to one argument, and also because I'm not a great fan of overusing operator overloading. I now see it as another variant of ptree syntax revolution, besides path class proposed by Matias Capeletto and Jeff Flinn. Finally I must say that no matter what result of the review will be, the interface of ptree will change, either as a preparation for a re-review, or as maintenance of boost library. There have been too many interesting interface propositions that are vastly superior to the current thing.
I certainly wouldn't try and read (and then write back!!!) the entire windows registry, or any large part of it, with this library.
Oh no, this would kill your windows box. Ptree does not support all registry data formats, so you would inevitably lose or corrupt some data. But the point is you can safely use JSON config on Linux version of your program, and registry config on Windows, with just 2 lines of difference between codebases. Thank you, Marcin

Marcin wrote:
Thank you for the time you took to review the library. Although your vote is no, you presented an interesting alternative to current syntax, and also to other proposed syntaxes. I was unaware of the potential that operator [] had in the context of ptree when coupled with overloaded, templated conversion operator and "ghost nodes". Originally I dismissed it, because it was limited to one argument, and also because I'm not a great fan of overusing operator overloading. I now see it as another variant of ptree syntax revolution, besides path class proposed by Matias Capeletto and Jeff Flinn.
I first have to state that i am not married with the operator/ solution, i just want to show some others conclusions that may help to redesigned the interface... The first thing is that i like the operator[] overload for ptree. I dont see it however as another variant to the path+operator/ proposal, but as a complementary sintaxis. I think of operator[] as a 'beatifull sintaxis' option to the get_child() function. IMO nobody will say that the following line... int i = mymap.get_child("fee").get_child("far").get_child("thumb").get(3); is better expressed by... int i = mymap["fee"]["far"]["thumb"].get(3); But if we want to support other things by means of this solution, like for example indexing, we will have to do something like... int i = mymap["fee"]["far",2].get(3); IMO this looks elegant, but because the operator[] return a ptree object, we just add another function to the ptree interface to support it. Every new feature we want to add to ptree in order to make it evolve to better things... we will need to add members in it. This is where the path concept will allow some magic. IMO the 'most' important property of the having a path class is the orthogonality that it introduce. Iterate with a path in a ptree and constructing a path are now two clearly separated process. I want to make this clear so i will redundantly state it: (A) Iterate in the ptree structure (B) Construct the path In the operator[] this two process are tighly coupled, we are in fact constructing the path 'by' iterating in the tree. I like this, i think i will use it for certain things, specially in recursive algorithms over the ptree. Is very efficient in this cases because we dont need to begin from the head of the ptree every time, we can save a reference to one of the ptree that is returned in the middle and use it. The orthogonality of the path class aliviate the ptree from constructing the path, now ptree only define one function that receive a path (we get rid of the get('/',key) familiy of functions). On the other hand we now have the possibility of constructing the path with different aproachs as we have show in others threads. We get 'x' separator support, indexing support, locale support, etc with out polute the ptree interface. What is more important, if Marcin makes it easy to define different path classes (i am confident that he will be able to do it) i will apreciate to have... using xml::comment; xml_path p; mymap.get( p / "fee" / "far" / "thumb ) // == get( "fee.far.thumb" ) mymap.get( p / "fee" / "far" + "name" ) // == get( "fee.far.<xmlattr>.name" ) mymap.get( p / "fee" / "far" + comment ) // == get( "fee.far.<xmlcomment>" ) the paths will asser if after the + is another / key sequence. And finally we can support something that i think everybody will just love to be able to do... typedef xContainer<key> container; container c; ... ptree::path p; mymap.get( p / "debug" / ptree::path( c.begin() , c.end() ) / "name" ); With this we now have the entire stl (and boost) containers playing along our lines! things can not get better... ( or can? this is, i think, the fifht message since Marcin says: "And the picture will hopely be complete" and beatifull things can not stopped to pop from the base of ptree.)

Matias Capeletto <matias.capeletto <at> gmail.com> writes:
This is where the path concept will allow some magic. IMO the 'most' important property of the having a path class is the orthogonality that it introduce. Iterate with a path in a ptree and constructing a path are now two clearly separated process.
I have to agree with this statement. I've been thinking that path should be a separate concept. Instances of the concept could even be worked on separately. I really see building a path much as you would build an expression template to be evaluated when invoked by the getter. I've been thinking something like: template<typename Path> ptree ptree::get(Path const& p) { return eval(p, *this); } One good concretization of this concept would be an xpath-like implementation. Some of the additions for this would be quite similar to what you mentioned in a previous thread with your overload of operator%. I would instead be interested in seeing operator[] implemented in this way since this is more xpath-like and IMHO easier to read. (Disadvantage is precedence.) That is xpath_expression& xpath_expression::operator[](where_expression const&). where_expression would be very similar to lambda. Based on your example from the other thread: data.get((p / "log")[_1 / "text" == "error1"] / "checked" + "id"); Following along the xpath and xslt I'd also like to see grouping. In my mind there should be an accessor to get the group resulting from evaluating the xpath expression. A group would be modeled by a view of ptree nodes. For: <p> <log><text>error1</text><checked id=0>foo</checked></log> <log><text>warning</text><checked id=1>foo</checked></log> <log><text>error1</text><checked id=2>foo</checked></log> <log><text>error1</text><unchecked id=3>foo</unchecked></log> </p> group result_group = tree.get((xpath("p") / "log")[_1 / "text" == "error1"] / "checked" + "id"); BOOST_FOREACH(ptree& result, result_group) { cout << result.data() << " "; } would output: "0 2". Just my 2c, -Ryan

I really see building a path much as you would build an expression template to be evaluated when invoked by the getter. I've been thinking something like:
template<typename Path> ptree ptree::get(Path const& p) { return eval(p, *this); }
This gives total control to the path classes... i haven't think in it before. It maybe be a very nice aproach...

"Matias Capeletto" <matias.capeletto@gmail.com> writes:
But if we want to support other things by means of this solution, like for example indexing, we will have to do something like...
int i = mymap["fee"]["far",2].get(3); ^ No can do. That's the comma operator.
-- Dave Abrahams Boost Consulting www.boost-consulting.com

On 4/25/06, David Abrahams <dave@boost-consulting.com> wrote:
"Matias Capeletto" <matias.capeletto@gmail.com> writes:
But if we want to support other things by means of this solution, like for example indexing, we will have to do something like...
int i = mymap["fee"]["far",2].get(3); ^ No can do. That's the comma operator.
Sorry about that... hopefully, it doesnt change what i was trying to say...
participants (8)
-
Baptiste Lepilleur
-
David Abrahams
-
John Maddock
-
Marcin Kalicinski
-
Matias Capeletto
-
Ryan Gallagher
-
Sebastian Redl
-
Thorsten Ottosen