User Tools

Site Tools


templated_covariant_pointer_type_in_c_with_simple_inheritance

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

templated_covariant_pointer_type_in_c_with_simple_inheritance [2017/09/05 08:46]
xylene created
templated_covariant_pointer_type_in_c_with_simple_inheritance [2017/09/05 09:18] (current)
xylene
Line 4: Line 4:
   * My solution to this is more about demonstrating the walking and iteration of data structures than any practical use   * My solution to this is more about demonstrating the walking and iteration of data structures than any practical use
   * Several C++ features are missing from this, notably multiple inheritance. Some curse at multiple inheritance but as C++ has only the single feature for object model programming (unlike other languages with built in "​category",​ "​interface",​ "​extends"​ features I use multiple inheritance a lot, especially for interfaces.   * Several C++ features are missing from this, notably multiple inheritance. Some curse at multiple inheritance but as C++ has only the single feature for object model programming (unlike other languages with built in "​category",​ "​interface",​ "​extends"​ features I use multiple inheritance a lot, especially for interfaces.
 +
 +Please find all the code for this on github at [[https://​github.com/​kuiash/​covariant_pointer_templates]].
 +
 +===Inheritance===
 +
 +Only single inheritance is shown in the example. Multiple inheritance is possible with the addition of type lists. More to come on that as it shows further interesting ways of walking data structures.
 +
 +To start with inheritance. A simple template is used to decorate each class with an **_t_inherits** type member that "​points"​ to the parent class. It also performs the actual inheritance for us. Note that I don't support protection. Why? Because I'm a [[http://​wiki.c2.com/?​CppHeresy|heretic]] and also it's out of scope of this exercise!
  
 <code cpp> <code cpp>
-#​include ​<ptr.h>+template<typename _parent>​ 
 +    struct inherits : _parent 
 +    { 
 +        using _t_inherits = _parent; 
 +    }; 
 +</code>
  
-int main(int argc, char argv[]) +===Object Type=== 
-{ + 
-    ptr<void_p_void; +So now I will define a basic **object*type that represents the top of the tree. All classes in this hierarchy will ultimately derive from this. 
-     + 
-    return -1; +Notice that it contains nothing. That is fine as it is used only for type comparison at compile time. 
-}+ 
 +<code cpp
 +struct object 
 +};
 </​code>​ </​code>​
 +
 +That's all I need. It just marks the top of the inheritance tree.
 +
 +===Test Hierarchy===
 +
 +Next I will define a simple class hierarchy that produces a tree at compile time that can be walked (upwards) by the **isa** code.
 +
 +<code cpp>
 +////////////////////////////////////////////////////////////////​
 +///
 +///            test hierarchy
 +///
 +///         ​.---------object----.
 +///         ​va ​                 vb
 +///         ​| ​                  |
 +///         ​vaa ​                ​vbb-----.
 +///                             ​| ​      |
 +///                             ​vbbb ​   vbbc
 +///
 +////////////////////////////////////////////////////////////////​
 +
 +struct va : inherits<​object>​
 +{ };
 +
 +struct vb : inherits<​object>​
 +{ };
 +
 +struct vbb : inherits<​vb>​
 +{ };
 +
 +struct vaa : inherits<​va>​
 +{ };
 +
 +struct vbbb : inherits<​vbb>​
 +{ };
 +
 +struct vbbc : inherits<​vbb>​
 +{ };
 +</​code>​
 +
 +===IS A, ISA, IS_A===
 +
 +Now we know this we can implement a template meta function that can tell us if a class of object of a class (use decltype or let the template processor do the work for you).
 +
 +<code cpp>
 +template<​typename _class, typename _isa> struct isa;
 +
 +template<>​
 +    struct isa<​object,​ object>
 +    {
 +        using _t_isa = t_true;
 +    };
 +
 +template<​typename _class>
 +    struct isa<​_class,​ _class>
 +    {
 +        using _t_isa = t_true;
 +    };
 +
 +template<​typename _class>
 +    struct isa<​object,​ _class>
 +    {
 +        using _t_isa = t_false;
 +    };
 +
 +template<​typename _class, typename _isa>
 +    struct isa
 +    {
 +        using _t_isa = typename isa<​typename _class::​_t_inherits,​ _isa>::​_t_isa;​
 +    };
 +</​code>​
 +
 +Let's not be scared yet!
 +
 +The first declaration simply defines what the template looks like. After this I use varying degrees of specialisation to provide the correct answers.
 +
 +The next template is the **isa** definition for **object == object**. Because, well, it is. Any object pointer is also an object pointer.
 +
 +The next template is invoked when both the **_class** and the **_isa** match. The equivalent of variable equality (that is the variables have different names but the same value). This represents a successful match. Clearly a pointer to **my_object** is also a pointer to **my_object**.
 +
 +The next template **<​object,​ _class>​** this means you have walked all the way to the top of the tree and not managed to find a match for **_isa**. In this case _t_isa it set the the false type.
 +
 +The final template is the one that does the actual work.
 +
 +It recursively walks up the tree using the **_t_inherits** type member of each class. When one of the above matches is found the **_t_isa** member is set to **t_false** or **t_true**.
 +
 +**t_true** and **t_false** are simple type wrappers around a true or false **bool** value. This can be decoded at runtime and compile time and used, for example, with [[http://​en.cppreference.com/​w/​cpp/​language/​static_assert|static_assert]].
 +
 +===Covariant Pointer Templates===
 +
 +So now we can determine if one class/​object is derived from another quite easily. We've seen how we can walk a tree (upwards) and return true/false values. One can even return multiple values from templates as they are named, recursion is possible and (as I will show in a later article) so are lists, stacks and more.
 +
 +<code cpp>
 +template<​typename _inner>
 +    struct ptr
 +    {
 +        using _t_inner = _inner;
 +        _inner * _data;
 +
 +        ptr() : _data(nullptr)
 +        { }
 +
 +        ptr(_inner * _data) : _data(_data)
 +        { }
 +
 +        template<​typename _t> ptr(const ptr<​_t>​ _that)
 +        {
 +            // note; rather annoyingly you can't put anything in
 +            // static_assert other than a string literal which
 +            // really limits what one can do (static const char * FTW??)
 +            static_assert(
 +                isa<_t, _inner>::​_t_isa::​_v,​
 +                "​Cannot cast pointer. Incompatible types"​);​
 +
 +            _data = _that._data;​
 +        }
 +    };
 +</​code>​
 +
 +What's going on here is that we have a pointer template. It is not a particularly complete definition but that's because I want to demonstrate the compile time failure due to incorrect inheritance.
 +
 +The important part of this is the templated constructor.
 +
 +The type **_t** in that definition is the **incoming** type. That is the type that is assigned from (source). The ptr being written to (**this**) has type **_t_inner** (outside the class) and **_inner** inside the class.
 +
 +The **isa** template is used to decide if the pointer can be upcast.
 +
 +If it can be then we just go ahead and let the compiler do it's job.
 +
 +If it can't then static_assert causes the compilation to finish and emit and error.
 +
 +Referring back the the test hierarchy we can see that, for example, all classes derive from **object**, **vbbc** derives from **vb** via *vbb*.
 +
 +Running the test app (see github link up above) shows this output. The actual test application is quite exhaustive.
 +
 +<code cpp>
 +"​(isa<​object,​object>::​_t_isa)"​ == t_true
 +"​(isa<​va,​object>::​_t_isa)"​ == t_true
 +"​(isa<​vaa,​object>::​_t_isa)"​ == t_true
 +"​(isa<​vb,​object>::​_t_isa)"​ == t_true
 +"​(isa<​vbb,​object>::​_t_isa)"​ == t_true
 +"​(isa<​vbbb,​object>::​_t_isa)"​ == t_true
 +"​(isa<​vbbc,​object>::​_t_isa)"​ == t_true
 +</​code>​
 +
 +<code cpp>
 +"​(isa<​object,​vbb>::​_t_isa)"​ == t_false
 +"​(isa<​va,​vbb>::​_t_isa)"​ == t_false
 +"​(isa<​vaa,​vbb>::​_t_isa)"​ == t_false
 +"​(isa<​vb,​vbb>::​_t_isa)"​ == t_false
 +"​(isa<​vbb,​vbb>::​_t_isa)"​ == t_true
 +"​(isa<​vbbb,​vbb>::​_t_isa)"​ == t_true
 +"​(isa<​vbbc,​vbb>::​_t_isa)"​ == t_true
 +</​code>​
 +
 +===Notes===
 +
 +  * Unordered List ItemError reporting is not fantastic but hopefully C++ **concepts** will help here, however, you'll have to wait until 2020 at least for really good compiler support in your own toolchains.
 +  * Down casting is not supported at all, this is a very different problem that requires run time understanding of objects. Often via a "​vtable"​. I'm seriously considering how those can be generated via templates!
 +  * static_assert has very limited ​
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
templated_covariant_pointer_type_in_c_with_simple_inheritance.txt · Last modified: 2017/09/05 09:18 by xylene