Introduction
This is the second part of a two part article series:
- 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. - 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:
- Test validity of an expression
- Get appropriate tag for a condition
- 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:
#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
#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.
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:
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:
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:
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:
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:
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:
- Sfinae is tricky for humans. For now, sfinae is tricky for compilers too, as we can see from a previous example.
- We cannot prioritize function overloads with sfinae. That is:
template<typename T>
auto f(const T& ob) -> decltype(cout << ob, void())
{
}
template<typename T>
auto f(const T& ob) -> decltype(ob.begin(), void())
{
}
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:
class tag_is_streamable{};
class tag_is_container{};
template<typename T>
void f(const T&, tag_is_streamable) {}
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:
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:
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:
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:
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:
struct book
{
int id;
string name;
string author;
};
book b;
Now, let’s create a tuple with the members of b
:
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.
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:
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:
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:
auto get_intros_type(book& val);
auto get_intros_type(const book& val);
Then we can do this:
auto intros_object = get_intros_type(b);
Here intros_object
is the following template type:
template<typename... TupleItems>
struct intros_type
{
std::string name; std::tuple<TupleItems...> items; };
And each of the TupleItems
is of the following type:
template<typename T>
struct intros_item
{
std::string name; T& val; };
And now, we can write a function like this:
void intros_example()
{
book b;
auto intros_object = get_intros_type(b);
cout << "Type name: " << intros_object.name << "\n";
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:
- Provide a way to populate ptree from the type
- Provide a way to populate the type from a
ptree
Now this task is trivial. All we have to do is:
- Get intros type for the structure
- Iterate over the structure, and for each item
- Get the name of the item
- Write the name, value pair to the tree (for goal 1)
- 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:
template<typename T>
boost::property_tree::ptree make_ptree(const T& in)
function to get ptree
for a type.
And we can write:
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.