AutomaticComponentToolkit icon indicating copy to clipboard operation
AutomaticComponentToolkit copied to clipboard

Add template classes

Open fsahmad opened this issue 4 years ago • 0 comments

Currently ACT does not support generic classes (aka template classes) which makes it hard to define certain types such as (type-safe) generic containers and algorithms.

For example, to define types like Map<int,IFoo*>, Map<string,IBar*>, and Map<int,double> in the API today, you would need to define the following:

    <class name="Map" abstract="true">
        <!-- Base methods that do not use the value type (Size, IsEmpty, etc.) -->
    </class>
    <class name="IntFooMap" parent="Map">
        <!-- Methods with key type="int32" and value type="class" class="Foo" -->
    </class>
    <class name="StringBarMap">
        <!-- Same methods copy-pasted, but with key type="string" and value type="class" class="Bar" -->
    </class>
    <class name="IntDoubleMap">
        <!-- Same methods copy-pasted, but with key type="int32" and value type="double" -->
    </class>

This quickly becomes unmaintainable as for N instantiations you need to:

  • Copy-paste and adjust the method definitions N times
  • Maintain N implementation classes by:
    • Writing the same code N times, or
    • Writing a generic Map class yourself and inheriting from that N times (something like class StringBarMap : public Map<std::string, IBar*>)

Instead, ACT could provide support for generic classes, and it could look something like this (following previous discussions with @martinweismann and @alexanderoster):

	<templateclass name="Map">
        <!-- 
            template<typename TKey, typename TValue> 
        -->
		<templateparam name="TKey" description="" />
		<templateparam name="TValue" description="" />

        <!-- template methods -->
		<templatemethod name="Add" description="adds an entry to the map.">
            <param name="Key" type="template" templateparam="TKey" pass="in" description="Key String."/>
			<param name="Value" type="template" templateparam="TValue" pass="in" description="Value to store."/>
		</templatemethod>

		<templatemethod name="Get" description="returns an entry of the map.">
			<param name="Key" type="template" templateparam="TKey" pass="in" description="Key String."/>
			<param name="Instance" type="template" templateparam="TValue" pass="return" description="Class Instance."/>
		</templatemethod>

		<templatemethod name="GetOut" description="returns an entry of the map.">
			<param name="Key" type="template" templateparam="TKey" pass="in" description="Key String."/>
			<param name="Instance" type="template" templateparam="TValue" pass="out" description="Class Instance."/>
		</templatemethod>

        <!-- non-template methods -->
		<method name="Clear" description="clears the map." />

		<method name="Count" description="returns the entry count of the map.">
			<param name="Count" type="uint32" pass="return" description="Entry count of the map."/>
		</method>
	</templateclass>

    <class name="IntFooMap" parent="Map">
        <templatearg param="TKey" type="int32"/>
        <templatearg param="TValue" type="class" class="Foo" />
    </class>

    <class name="StringBarMap" parent="Map">
        <templatearg param="TKey" type="string"/>
        <templatearg param="TValue" type="class" class="Bar" />
    </class>

    <class name="IntDoubleMap" parent="Map">
        <templatearg param="TKey" type="int32"/>
        <templatearg param="TValue" type="double" />
    </class>

The implementation stubs could then be generated as follows:

// Implementation Stubs
namespace Component { namespace Impl {

template <typename TKey, typename TValue>
class CMap 
{
public:
    virtual void Add(TKey Key, TValue Value);

    virtual TValue Get(TKey Key);

    virtual void GetOut(TKey Key, TValue *pValue);

    virtual void Clear();

    virtual Component_uint32 Count();
};

class IntFooMap : public CMap<Component_int32, IFoo *>
{
    /* instantiates to:
    virtual void Add(Component_int32 Key, IFoo * Value);

    virtual IFoo * Get(Component_int32 Key);

    virtual void GetOut(Component_int32 Key, IFoo **pValue);

    virtual void Clear();

    virtual Component_uint32 Count();
    */
};


class StringBarMap : public CMap<String *, IBar *>
{
    /* instantiates to:
    virtual void Add(String * Key, IBar * Value);

    virtual IBar * Get(String * Key);

    virtual void GetOut(String * Key, IBar **pValue);

    virtual void Clear();

    virtual Component_uint32 Count();
    */
};

class IntDoubleMap : public CMap<Component_int32, double>
{
    /* instantiates to:
    virtual void Add(Component_int32 Key, double Value);

    virtual double Get(Component_int32 Key);

    virtual void GetOut(Component_int32 Key, double *pValue);

    virtual void Clear();

    virtual Component_uint32 Count();
    */    
};

}} // namespace Component::Impl

And the bindings:

// Bindings
namespace Component { namespace Binding {

class CMap 
{
public:
    virtual void Clear();

    virtual Component_uint32 Count();
};

class IntFooMap : public CMap
{
    virtual void Add(Component_int32 Key, IFoo * Value);

    virtual IFoo * Get(Component_int32 Key);

    virtual void GetOut(Component_int32 Key, IFoo **pValue);
};


class StringBarMap : public CMap
{
    virtual void Add(String * Key, IBar * Value);

    virtual IBar * Get(String * Key);

    virtual void GetOut(String * Key, IBar **pValue);
};

class IntDoubleMap : public CMap
{
    virtual void Add(Component_int32 Key, double Value);

    virtual double Get(Component_int32 Key);

    virtual void GetOut(Component_int32 Key, double *pValue);    
};

}} // namespace Component::Binding

The API author would then only need to implement the template class CMap once.

Notes:

  • Only works with strings if they're wrapped in a class
    • Already a need for string wrapper class
    • Can't support string without wrapper class because of different in/out/return types (const std::string& / std::string & / std::string)
      • Template class signatures would differ from the simple / class types, won't work.
  • Ref counting, need to check if type is a pointer or not to decide whether or not to call IncRefCount/DecRefCount.
    • Provide a helper function with specializations for class / simple types
  • Concepts
    • Example: key type for map, how to compare?
      • pLhs->Compare(pRhs)?
      • pLhs < pRhs?
    • Leave it up to the API author, C++ (pre C++20) didn't have support for Concepts either and relied on documentation
      • API author can choose to write template functions with specializations like (ACT could document an example)
        template <typename T, typename = void>
        struct compare_helper {
            static int compare(T lhs, T rhs);
        };
        
        template <typename Comparable>
        struct compare_helper<Comparable, std::enable_if_t<std::is_base_of_v<IBase, std::remove_pointer_t<Comparable>>>> {
            static int compare(Comparable pLhs, Comparable pRhs) {
                static_assert(std::is_member_function_pointer<decltype(&std::remove_pointer_t<Comparable>::Compare)>::value,
                            "Type does not implement Comparable concept (Comparable::Compare is not a member function)."); 
                return pLhs->Compare(pRhs);
            }
        };
        
        template <typename Comparable>
        struct compare_helper<Comparable, std::enable_if_t<std::is_arithmetic_v<Comparable>>> {
            static int compare(Comparable lhs, Comparable rhs) {
                return lhs - rhs;
            }
        };
        
        template <typename T>
        int compare(T lhs, T rhs) {
            return compare_helper<T>::compare(lhs,rhs);
        }
        // compare(pObject1, pObject2) => compiles only if pObject1 has Compare method
        // compare(1,2)
        

fsahmad avatar Dec 16 '20 15:12 fsahmad