programming c++ tree swiftree boost c++11 template configuration cplusplus cpp

swiftree

a concise way to load values from tree structures, like xml and json

I have been dealing for years with applications needing to load values from configuration files like xml and json, but I've never found any practical and non-invasive way to do it.

I've used xerces-c and tinyxml, and then moved on the handier (but less powerful) boost::ptree.

They have always worked fine, but I've never been completely satisfied: to assign a value loaded from a file to the property of an object should be trivial in C++, as it is in Javascript or Ruby. On the contrary the code to write to do such a simple thing, except when using macros (AAARRRGHH!) or a wrapper class, is always too much.

I just want to map something written in an xml file (or json, or yaml) to the property of an object or to a local variable!

You can find an example in my project threescanner where I use a wrapper class (called Config) of boost :: ptree to load configurations:

void RealProjector::setupWindow(const Config& cfg) {
  auto width = cfg.get<int>("width");
  auto height = cfg.get<int>("height");
  auto antialiasing = cfg.get<int>("antialiasing", 0);
  auto name = cfg.get<std::string>("name");

(You can see the complete example here. And here the json file)

Or, better yet, in ThreephaseEngine where I pass cfg to the base class too, assigning its value directly to a property:

ThreephaseEngine::ThreephaseEngine(const Config& cfg) :
    Engine(cfg),
    wrapMethod_(cfg.get<int>("wrapMethod")),

(You can see the complete example here.)

So far boost :: ptree was the most concise way I've found to express what I want. It works pretty well, especially when used with C++11 auto, but in the old standard would have been

int width = cfg.get<int>("width");

where the int type is repeated, and I don't like repetition!

So I decided to find an alternative, where maybe the type of casting to do is deduced from the value on the left hand of the operator.

The C++ language has strong typing, thus it's kind of "clunky" to cast something into something else. And since the configuration files are nothing more than trees of strings, or better, maps whose keys are strings and values are maps or other strings, then the translation(cast?) of the value will be from string to T, where T is the type we need. Or from tree to T.

For the former boost::lexical_cast is good enough... but for the latter?

I mean... working with Ogre3D I've often had to handle vectors and quaternions, so the the solution would have been:

Ogre::Vector3 position(cfg.get<float>("position.x"),
  cfg.get<float>("position.y"),
  cfg.get<float>("position.z"));

A naïve solution is using a macro to express the concept (some years ago I used to fall into temptation).

#define XML_VECTOR3(key) \
  Vector3 key(cfg.get<float>(#key ".x"), \
      cfg.get<float>(#key ".y"), \
      cfg.get<float>(#key ".z"))

Which let me write this:

XML_VECTOR3(position);

Too concise, and primarily too dangerous: it's a macro!

As a general rule, if you can solve a problem with a macro, there is probably a better solution which uses templates.

And that's how I come to design swiftree which let me write something like:

float speed = cfg["car.speed"];
Ogre::Vector3 position = cfg["car.position"];

Or, better yet, with default values and properties initialization:

Car(const Tree& cfg) : 
  speed_(cfg.value("speed", 0)),
  position_(cfg.value("speed", Ogre::Vector3::ZERO)) {

  }

We must thanks templates for the magic, especially the conversion operator, which provides an effective way to convert a tree (or a simple string) to any type.

template<class Type>
operator Type() const {
  return boost::lexical_cast<Type>(this->toString());
}

Maybe it's a little esoteric, and I'm not definitely sure about its safety, but it seems to do its job!

It works for all types supported by boost::lexical_cast... but for all the others? And for custom classes using more values, e.g. Vector3?

We can specialize the template:

namespace swiftree {
template<>
Tree::operator Ogre::Vector3() const {
    return Ogre::Vector3(
      value<float>("x"),
      value<float>("y"),
      value<float>("z")
    );
}
}

Now we can define a translator for every structure!

Why the name swiftree? Because it works with tree data structures and it's effective and swift to use.

You can find more information on what swiftree can do in the GitHub project. Do you have any advice or request? Leave a comment here, or better yet create an issue on GitHub!

Ti è piaciuto l'articolo? Condividilo! Commentalo!

comments powered by Disqus

Article available in other languages
it