/
traits
traits
Developing traits
// templates-typedefs.cpp : This file contains the 'main' function. Program execution begins and ends there. // #include "pch.h" #include <iostream> #include <string> #include <limits> using namespace std; //++ Templated code ... /* The significance of using typedefs in templates can be seen by the code below. It may not seem important at first, but it allows one template class to be defined in terms of the data types that are being used in another class i.e. the one that was passed in as a template parameter. So what? */ template <typename T> class SubElement { public: typedef T _type; SubElement(_type value) :data(value) { return; } private: _type data; }; template <typename T> class Container { public: typedef T _type; //Container(_type value) :data(value) { return; } private: //_type data; }; // Partial instantiation template<> class Container<SubElement<int>> {}; template <typename T> class Component { public: typedef T itsT; // By using typedefs and templates together, one can create a class and // use its data members types in other classes public: Component(itsT value) :data(value)//, elementData(value) { return; } itsT getData() const { return data; } private: itsT data; //Container< SubElement<itsT> > elementData; // This declaration creates an instance of a Container class that contains a type // Component< T > object, but the data members of Component are actually typed // on whatever the types are within Component and not Component< T > itself. }; template <class K, class V> class KeyValueMap { public: KeyValueMap(K key, V value) :itsKey(key), itsValue(value) { return; } private: K itsKey; V itsValue; }; template <class K, class V> KeyValueMap< K, V >& Map(K key, V value) { return *new KeyValueMap< K, V >(key, value); } /* The following classes are a demonstration of what we talking about in terms of modelling the command object and its related objects */ class TCommand; class TCommandType { protected: TCommandType(string& cmd); public: const string& getCommand() const; virtual void executeCommand(TCommand& theCmd) = 0; private: string itsCmd; }; class TStoredProcCmd : public TCommandType { public: TStoredProcCmd(string cmd); void executeCommand(TCommand& theCmd); }; class TSQLCmd : public TCommandType { public: TSQLCmd(string cmd); void executeCommand(TCommand& theCmd); }; class TDataBaseType { public: TDataBaseType(string dbType, TCommandType& cmdMode) : itsDBType(dbType), itsCmdMode(cmdMode) { return; } const string& getDBType() const { return itsDBType; } TCommandType& getCmdType() const { return itsCmdMode; } private: string itsDBType; TCommandType& itsCmdMode; }; class TCommand { public: TCommand(TDataBaseType& dbType, string cmd) :itsDBType(dbType), itsCmd(cmd) { return; } void execute(); void executeCommand(TStoredProcCmd& cmdType); void executeCommand(TSQLCmd& cmdType); private: TDataBaseType& itsDBType; string itsCmd; }; //============================================================================ /* TCommandType */ TCommandType::TCommandType(string& cmd) :itsCmd(cmd) { return; } const string& TCommandType::getCommand() const { return itsCmd; } /* TStoredProcCmd */ TStoredProcCmd::TStoredProcCmd(string cmd) :TCommandType(cmd) { return; } void TStoredProcCmd::executeCommand(TCommand& theCmd) { theCmd.executeCommand(*this); } /* TSQLCmd */ TSQLCmd::TSQLCmd(string cmd) :TCommandType(cmd) { return; } void TSQLCmd::executeCommand(TCommand& theCmd) { theCmd.executeCommand(*this); } /* TCommand */ void TCommand::execute() { itsDBType.getCmdType().executeCommand(*this); } void TCommand::executeCommand(TStoredProcCmd& cmdType) { // do the actual execute in here ... } void TCommand::executeCommand(TSQLCmd& cmdType) { // do the actual execute in here ... } // Before we get into developing an example of a trait let's refesh on template classes // typically template classes are defined as shown here template <typename T> class Holder { public: Holder(){} void execute() { cout << "Holder T" << endl; } }; // And this would be a template specialization, it's designed specifically to handle // integers template <> class Holder<int> { private: int value; public: Holder(){} void execute() { cout << "Holder int" << endl; } }; // But templates can go further than this, see below. In this example we narrow // the template type to a bool, it could be any type template <bool b> class Element { public: Element() {} void execute() { cout << "Element<bool b>" << endl; } }; // We can specialize the above template on a bool value template <> class Element<true> { public: Element() {} void execute() { cout << "Element<true>" << endl; } }; // Going to develop the example from ACCU // First create a really simple template for testing whether a type can be void template< typename T > struct _is_void{ static const bool value = false; }; // Now create a template specialization that must yield a true to the question // That is void itself must yield true, all other parameters must yield false template<> struct _is_void< void > { static const bool value = true; }; template <typename T> bool isVoid() { return _is_void<T>::value; } // We now have a complete traits type that can be used to detect if any given type, // passed in as a template parameter, is void. Not the most useful piece of code on // its own, but definitely a useful demonstration of the technique. // Here is another example testing if a type is a pointer template< typename T > struct _is_pointer { static const bool value = false; }; // Here is our template specialization // Notice here the template parameter cannot be empty because the type is needed // On the struct we simply add the * to give this it's specialization template< typename T > struct _is_pointer< T* > { static const bool value = true; }; // Here is our test function template <typename T> bool isPointer(T data) { return _is_pointer<T>::value; } //-------------------------------------------------------------------------------// // Example of a trait being used, found on accu.org //-------------------------------------------------------------------------------// template< class T > T findMax(const T * const data, const size_t numItems) { // Obtain the minimum value for type T // This is an example of a trait, T which is findMax template parameter // is being used as the type info for numeric_limits<T> // begin trait T largest = std::numeric_limits< T >::min(); // end trait for (unsigned int i = 0; i < numItems; ++i) if (data[i] > largest) largest = data[i]; return largest; } //-------------------------------------------------------------------------------// // Example of designing a trait, found on accu.org // An algorithm selector //-------------------------------------------------------------------------------// /* In this example we going to build a demo classs called algorithm_selector that is called from a generic method called algorithm. An algorithm fx is passed an object that is used as part of the algorithm fx, it simply wants the passed object to perform an operation to fulfill the requirement of the algorithm. We will call this operation algorithm_part() The object passed to the algorithm function must indicate whether or not it has an optimised implementation. The algorithm_selector makes use of this information to determine whether or not it should provide a basic section of the desired requirement or if it should delegate this to the object passed into the algorithm function. */ template< typename T > struct supports_optimised_implementation { // The static is important, it allows us to use compile time switching static const bool value = false; }; template< bool b > struct static_algorithm_selector { template< typename T > static void algorithm_part(T& object) { //implement the alorithm operating on "object" here cout << "performing non optimised part of algorithm [static]" << endl; } }; template<> struct static_algorithm_selector< true > { template< typename T > static void algorithm_part(T& object) { object.optimised_implementation_algorithm_part(); } }; template< bool b > struct dynamic_algorithm_selector { template< typename T > static void algorithm_part(T& object) { //implement the alorithm operating on "object" here cout << "performing non optimised part of algorithm [dynamic]" << endl; } }; template<> struct dynamic_algorithm_selector< true > { template< typename T > static void algorithm_part(T& object) { object.optimised_implementation_algorithm_part(); } }; template< typename T > void algorithm_v1(T& object) { // The supports_optimised_implementation< T >::value will instantiate the object // and set ::value to either true or false, we saw that above with // _is_void<> and _is_pointer<> examples // // The algorithm_selector has been templatised to accept a boolean value only static_algorithm_selector< supports_optimised_implementation< T >::value >::algorithm_part(object); } template< typename T > void algorithm_v2(T& object) { // The supports_optimised_implementation< T >::value will instantiate the object // and set ::value to either true or false, we saw that above with // _is_void<> and _is_pointer<> examples // // The algorithm_selector has been templatised to accept a boolean value only dynamic_algorithm_selector< supports_optimised_implementation< T >::value > das; das.algorithm_part(object); } // Our test classes class ObjectA { public: void no_optimised_implementation() { //this never gets called so can delete it... } }; class ObjectB { public: void optimised_implementation_algorithm_part() { cout << "performing optimised part of algorithm" << endl; } }; // Create a template specialization of our supports_optimised_implementation<T> // By doing this we guarantee that when supports_optimised_implementation<> is used // in the algorithm function the correct version X_algorithm_selector<> is compiled // into the code template<> struct supports_optimised_implementation< ObjectB > { // The static is important, it allows us to use compile time switching static const bool value = true; }; //---------------------------------------------------------------------------// int fooBar() { cout << "fooBar()" << endl; return 0; } template<class _Ty> class crazy_trait { // numeric limits for arbitrary type _Ty (say little or nothing) public: static constexpr _Ty(fooBar)() noexcept { // return minimum value return (_Ty()); } }; //--------------------------------------------------------------------------- int main() { auto val = crazy_trait<int>::fooBar(); Holder<int> t1; t1.execute(); Holder<char> t2; t2.execute(); Element<false> e1; e1.execute(); Element<true> e2; e2.execute(); // Work through above example above before looking at the next set of examples // below // This frst example makes use the numeric_limits<> as trait std::numeric_limits<int> il; cout << std::numeric_limits<int>::min() << "/" << il.max() << endl; int data[] = { 3, 7, 99, 34, 2, 78, 101, 250, 178 }; int count = sizeof(data) / sizeof(int); int max = findMax(data, count); //-----------------------------------------------------------------------------// // Now we're going to develop our own traits //-----------------------------------------------------------------------------// // Using the is_void template _is_void<int> test; test.value; // The function adds very little value... bool result = isVoid<void>(); // Here the template function adds a lot of value, you don't need to know // the data type at design time, the compiler can perform the diagnostics for you // by looking at the varibale itself result = isPointer(data); result = isPointer(count); result = isPointer("Hello world"); // Now we move onto building a more substantial trait, look at the following // classes above // supports_optimised_implementation // static_algorithm_selector // static_algorithm_selector< true > // ObjectA a; algorithm_v1(a); // calls default implementation ObjectB b; algorithm_v1(b); // calls ObjectB::optimised_implementation(); algorithm_v2(a); // calls default implementation algorithm_v2(b); // calls ObjectB::optimised_implementation(); SubElement<int>::_type x = 5; //Container< SubElement<int> > elementData(5); //Component<int> fb1(5); /* int i = fb1.getData(); Container<Component<int>> fbu(5); Component<int>::itsT x = i; Component<KeyValueMap<int, int>> fb2(KeyValueMap<int, int>); */ std::cout << "Hello World!\n"; } //---------------------------------------------------------------------------