Click here to Skip to main content
15,885,110 members
Articles / Programming Languages / C++14

Populating Structure from XML, The Automatic Way (Part 2 of 2)

Rate me:
Please Sign up or sign in to vote.
4.75/5 (3 votes)
2 Feb 2016CPOL7 min read 13.8K   128   9  
intros_ptree: A library that lets you populate your structure or class from XML file (or json or ini file) automatically, and vice versa

Introduction

This is the second part of a two part article series:

  1. In the previous part (link), we have seen how we can use the intros_ptree library to populate a structure (or class) from XML file (or json or ini file) automatically.
  2. In this part, we will see how the library does that.

You can find the examples in this article in utils_examples/utils_examples.cpp file.

Attached with the article is intros_ptree library, with examples on how to use it,

Overview

Quote:

Question: How do you eat an elephant?

Answer: One bite at a time.

Before we start solving the problem, we need to develop some helper tools. With these tools, we can:

  1. Test validity of an expression
  2. Get appropriate tag for a condition
  3. Devise a mechanism for type introspection

And then, we will see how we can easily achieve our goal using our newly developed tools. Let's begin.

Solution

Test Validity of an Expression

SFINAE: If you don’t know what that is, it’s going to blow your mind. It lets you write the wrong code!!! And the compiler will silently ignore it. (That doesn't sound right!!! If the compiler ignores it, and that's the standard behavior, then that means the code is correct.)

Basically, we use sfinae to choose the right overload (or specialization). The wrong overload will have an expression that is wrong (uncompilable). But because of sfinae, rather than giving a compile error, the compiler will silently ignore it. Hence the name "Substitution failure is not an error".

Let's see some sfinae in action.

Let’s say, we want to check if a type has a member variable x. We can do that using sfinae, in the following way:

C++
#include <type_traits>

template<typename... Ts> struct make_void { typedef void type; };
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

template<typename T, typename = void>
struct has_x : std::false_type {};

template<typename T>
struct has_x<T, void_t<decltype(T::x)>> : std::true_type {};

int main()
{
    struct A { int x; int y; };
    static_assert(!has_x<int>::value, "");
    static_assert(has_x<A>::value, "");
}

This is valid C++ code. But this fails with Visual studio 2015, update 1. More unfortunately, the failure is inconsistent. This may work for a while and start failing later.

So, we need some alternative to solve this problem.

Quote:

Any problem can be solved with an extra level of indirection

C++
#include <type_traits>

template<typename... Ts> struct make_void { typedef void type; };
template<typename... Ts> using void_t = typename make_void<Ts...>::type;

template<typename T, typename = void_t<decltype(T::x)>>
std::true_type has_x_impl2();

template<typename T>
decltype(has_x_impl2<T>()) has_x_impl1(void*);

template<typename T>
std::false_type has_x_impl1(...);

template<typename T>
struct has_x : decltype(has_x_impl1<T>(nullptr))
{};

int main()
{
    struct A { int x; int y; };
    static_assert(!has_x<int>::value, "");
    static_assert(has_x<A>::value, "");
}

Awesome!!! We now know, A has a member x.

Only that, this code is not very fun to write.

So, here is a helper macro to make our life easy.

C++
IMPLEMENT_EXPR_TEST(name, test, ...)

This macro is available in utils/is_valid_expression.hpp file.

With the help of the macro, now we check, if a type has member x like this:

C++
IMPLEMENT_EXPR_TEST(has_x, decltype(T::x), T)
static_assert(has_x<A>::value, "");

Nice… that’s better.

Let’s look at another example:

Let’s say you want to perform the following check:

C++
static_assert(has_begin<T>::value, "");

Where, has_begin should say whether type T has begin member function. Then, you need to use IMPLEMENT_EXPR_TEST like this:

C++
IMPLEMENT_EXPR_TEST(has_begin, decltype(declval<T&>().begin()), T)

Here, the first parameter is the name of the test. The second parameter is the actual expression test that you want to perform. And the third parameter is the one or more types you used in expression test.

Let me show another way we can perform the above test:

C++
template<typename T> using has_begin_test = decltype(declval<T&>().begin());

IMPLEMENT_EXPR_TEST_INDIRECT(has_begin, has_begin_test, T)

static_assert(has_begin<T>::value, "");

Here, we are using alias template as the expression test.

This could be useful, when your expression is more complex.

You can find more examples on expression test in utils_test/test_is_valid_expression.cpp file.

Get an Appropriate Tag for a Condition

Tag dispatch: Tag dispatch is a mechanism we use to select the right overloaded function. Let’s see an example:

C++
Template<typename T>
void f(const T& ob, tag_type_is_container);

Template<typename T>
void f(const T& ob, tag_else);

Here, we see two overloads of a function f. One we want to choose when T is a container. The other one we want to use in every other situation.

We could use sfinae to achieve this. But tag dispatching has some benefits over sfinae. For example:

  1. Sfinae is tricky for humans. For now, sfinae is tricky for compilers too, as we can see from a previous example.
  2. We cannot prioritize function overloads with sfinae. That is:
C++
template<typename T>
auto f(const T& ob) -> decltype(cout << ob, void())
{
    // choose this if we can stream ob to cout
}

template<typename T>
auto f(const T& ob) -> decltype(ob.begin(), void())
{
    // choose this if ob has begin member
}

f(std::string());

The above code won’t compile. That’s because, for std::string, both f overloads satisfy our sfinae condition. So, compiler can’t pick one overload over the other. And user can’t say, to choose streamable (or the one with begin member function) in this situation.

Now, let’s try the above with tag dispatching.

What we want to write is this:

C++
// these empty classes is what we are calling tags
// they exist only to help us choose the right overloaded function
class tag_is_streamable{};
class tag_is_container{};

template<typename T>
void f(const T&, tag_is_streamable) // we don't even have to give a variable name for the tag
{}

template<typename T>
void f(const T&, tag_is_container)
{}

std::string s;

f(s, my_tag<T>());

Here, my_tag is the puzzle we need to solve. We want to write it in a way, such that, we can pair our conditions with tags, and whichever condition gets satisfied first, we get the corresponding tag. Like this:

C++
template<typename T>
using my_tag = get_tag<
    is_streamable<T>, tag_is_streamable,
    is_container<T>, tag_is_container
>;

We can implement the checks like this:

C++
IMPLEMENT_EXPR_TEST(is_streamable, 
decltype(std::declval<std::ostream&>() << std::declval<T&>()), T)
IMPLEMENT_EXPR_TEST(is_container, decltype(std::declval<T&>().begin()), T)

So, we have paired our checks with the tags. But how to write get_tag?

std::disjunction

Disjunction is a C++17 feature. cppreference gives a possible implementation for it like this:

C++
template<class...> struct disjunction : std::false_type { };
template<class B1> struct disjunction<B1> : B1 { };
template<class B1, class... Bn>
struct disjunction<B1, Bn...> : 
std::conditional_t<B1::value != false, B1, disjunction<Bn...>> { };

So, what it does is, for every type T, it checks if T::value is true. If it is, then it returns that type. Or to be more accurate, it inherits from the type. So, with this, we can get the first type that has ::value true.

What we want is very close to this. The only difference is, instead of returning the type that satisfies the condition, we want the type paired with it.

As it happens, by taking inspiration from disjunction, we can easily implement get_tag. Here is the implementation:

C++
template<class...> struct get_tag {};
template<class def_tag> struct get_tag<def_tag> : def_tag {};
template<class cond1, class tag1, class... Bn>
struct get_tag<cond1, tag1, Bn...> : 
std::conditional_t<cond1::value != false, tag1, get_tag<Bn...>> {};

Great!!! We have our get_tag implementation now.

get_tag is available in utils/util_traits.hpp file, under utils::traits namespace.

You can find more examples on get_tag in utils_test/test_util_traits.cpp file.

Device a Mechanism for Type Introspection

Reflection: Wikipedia says this about reflection: “In computer science, reflection is the ability of a computer program to examine (see type introspection) and modify its own structure and behavior (specifically the values, meta-data, properties and functions) at runtime.[1]

In some languages, we can query for all the members of a type, but C++ is not one of those languages. So, let’s see what we can do about this.

std::tuple is a class that can hold values of different types. So, if we could populate a std::tuple with all the members of a struct, then we could query for that type’s members!!!

Here is how we can do that. Let’s look at our book struct again:

C++
struct book
{
    int id;
    string name;
    string author;
};
book b;

Now, let’s create a tuple with the members of b:

C++
auto t = tuple<string, int, string>(b.author, b.id, b.name);

And there we go. A tuple with all the members of book.

But making modifications in t won’t be reflected in b. And we want that.

To get that, we need to make tuple items reference.

C++
auto t = tuple<string&, int&, string&>(b.author, b.id, b.name);

and now, changes in t will be reflected in b. Awesome!!!

And using boost fusion library’s for_each function, we could even iterate over a tuple.

The following 4 macros generalize the concept so that we could easily use it for any structure:

C++
BEGIN_INTROS_TYPE(type)
BEGIN_INTROS_TYPE_USER_NAME(type, name)
END_INTROS_TYPE(type)
ADD_INTROS_ITEM(x)
ADD_INTROS_ITEM_USER_NAME(x, name)

Once we say:

C++
BEGIN_INTROS_TYPE(book)
    ADD_INTROS_ITEM(id)
    ADD_INTROS_ITEM(name)
    ADD_INTROS_ITEM(author)
END_INTROS_TYPE(book)

Then, two functions are defined for book like below:

C++
auto get_intros_type(book& val);
auto get_intros_type(const book& val);

Then we can do this:

C++
auto intros_object = get_intros_type(b);

Here intros_object is the following template type:

C++
template<typename... TupleItems>
struct intros_type
{
	std::string name; // the type name
	std::tuple<TupleItems...> items; // members of the type
};

And each of the TupleItems is of the following type:

C++
template<typename T>
struct intros_item
{
    std::string name; // name of the item
    T& val; // member value as reference... this will be const reference if type is const
};

And now, we can write a function like this:

C++
void intros_example()
{
	book b;

    // if you think auto is not a very useful addition to C++
    // then try cout << typeid(intros_object).name()
    // auto just saved us from writing that horrible type
	auto intros_object = get_intros_type(b);

	cout << "Type name: " << intros_object.name << "\n";

	// set values
	auto t = intros_object.items;
	
	std::get<0>(t).val = 1234;

	cout << "Name of item: " 
	<< std::get<0>(t).name << "\n";
	cout << "Value from b: " << b.id << 
	"\tValue from intros: " << std::get<0>(t).val << "\n";
}

And this will print:

Type name: book
Name of item: id
Value from b: 1234      Value from intros: 1234

You can find more examples on type introspection in utils_test/test_intros_type.cpp file.

Note: There are other libraries that provide type introspection support (boost fusion, boost hana for example). The reason I provided my own implementation of type introspection is, I wanted to allow different name for a variable (BEGIN_INTROS_TYPE_USER_NAME and ADD_INTROS_ITEM_USER_NAME).

Back to Our Original Problem

Now that we have our implementation for intros type, expression test and get_tag, now we are ready to go back to our original problem.

Goal:

For any structure or class, that has intros support:

  1. Provide a way to populate ptree from the type
  2. Provide a way to populate the type from a ptree

Now this task is trivial. All we have to do is:

  1. Get intros type for the structure
  2. Iterate over the structure, and for each item
    1. Get the name of the item
    2. Write the name, value pair to the tree (for goal 1)
    3. Get the value for that name from the tree and write in the structure (for goal 2)

We also have to categorize the items (like streamable item, container item, etc.). Using our expression test and
get_tag tools, we can easily do that. I don’t want to bore you with the details of how each item type is handled. You can check it from the source.

And that’s it. Now we can write:

C++
template<typename T>
boost::property_tree::ptree make_ptree(const T& in)

function to get ptree for a type.

And we can write:

C++
template<typename T>
T make_intros_object(const boost::property_tree::ptree& tree)

function to get a type populated from ptree.

Conclusion

I had a lot of fun developing the library. I hope you enjoy using it too. Let me know anything you have to say in the comments section.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Bangladesh Bangladesh
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --