Type Traits By Reggie Meisler
What Type Traits can give us information about a type. Generally used on template types where the type could be anything.
How Template specialization Compiler Intrinsics Hax
Why Transparent optimizations Safer public interfaces / Better error-handling (Concepts) Uniform public interface
Swap Example template <typename T> void Swap(T& a, T& b) { const T temp = a; a = b; b = temp; }
Optimized Swap template <> void Swap(int& a, int& b) { if( a == b ) return; a ^= b; b ^= a; a ^= b; }
Basic Introspection Sometimes it may be useful to know about a type’s qualifiers. This is easily accomplished via partial template specialization.
IsPointer trait template <typename T> struct IsPointer { static const bool value = false; }; template <typename T> struct IsPointer<T*> { static const bool value = true; };
IsPointer Trait int main() { std::cout << “Is char* a pointer?” << IsPointer<char*>::value << std::endl << “Is char a pointer?” << IsPointer<char>::value << std::endl; return 0; }
Basic Introspection cont. The power of type introspection is the ability to observe something about a type non-intrusively and without causing an error. If you’re library doesn’t support a certain type, you can make the user aware of that without having them “break” your function.
Controlling Interface template <typename T> void Serialize(T& data) { // Doesn’t know how to deal with // pointers… Let the client deal with it? // … }
Controlling Interface The problem isn’t so much “What if the user sees ugly compiler template errors?!” It’s more like, “What if the user doesn’t get any errors at all?” If your code made it to runtime and crashed, your interface is unsafe!
Controlling Interface template <typename T> void Serialize(T& data) { // Take error handling into your own // hands Assert( IsPointer<T>::value ); // … }
Type Transformation More often, rather than performing different logic based on some type info, we’d prefer to simply change the type we’re working with. This allows us to “restrict” or “filter” what types our templatized class/function will actually deal with!
But How??? The main idea is to have your type trait class provide a similar type as to what was passed to it, but with different qualifiers. This is usually done with a typedef and/or partial specialization.
Problem String literals are interpreted as const char arrays (Of whatever number of characters you made them + 1) by the compiler. This means that if you pass a string literal through a template function your resulting type will often be “undesired”.
Example template <typename T> SmartPtr<T> MakeHandle(T& data) { return SmartPtr<T>(data); } // Doesn’t work! Type mismatch! SmartPtr<const char*> sPtr = MakeHandle(“Noice”);
Example cont. The SmartPtr that was returned looked like this: SmartPtr<const char[6]> We wanted a SmartPtr like this: SmartPtr<const char*> Oh woes us! Surely we’ll need to sacrifice usability or write a special version of our function to take care of this!
ArrayToPtr to the Rescue! template <typename T> struct ArrayToPtr { typedef T type; }; template <typename T, int N> struct ArrayToPtr<T[N]> { typedef T* type; };
Solution template <typename T> SmartPtr<typename ArrayToPtr<T>::type> MakeHandle(T& data) { return SmartPtr <typename ArrayToPtr<T>::type>(data); } // Works! Literal array transformed to ptr! SmartPtr<const char*> sPtr = MakeHandle(“Noice”);
Noice It’s important to note that types who are not arrays will not be affected by this transformation at all. Remember, we partially specialized for array types in order to perform our qualifier stripping.
A New Problem Storing template member variables without some sort of wrapper class is often avoided. A major problem in storing types like this comes from const and reference types. These types require the parent class to implement a non-trivial constructor.
Template Member Variables template <typename T> class MyContainer { T m_theData; }; // ERROR: Requires non-trivial ctor! MyContainer<const int&> c;
Type Traits to the Rescue! Being the super clever people we are, we can see how a type transformation will fix this problem lickidy split! But we’re still kinda new to this type traits junk– let’s see what we can come up with.
CS225 All Over Again… Why does the following type trait not work? template <typename T> struct NoConstRef { typedef T type; }; template <typename T> struct NoConstRef<const T&> { typedef T type; };
Solution We need to first strip off the reference in order to make the const qualifier “visible”. This means we’ll need to use two type traits in tandem. template <typename T> struct NoConstRef<T> { typedef typename NoConst <typename NoRef<T>::type>::type type; };
Template Member Variables FTW Our class will now strip off the const and/or reference qualifiers from types whenever it’s called for. template <typename T> class MyContainer { typename NoConstRef<T>::type m_theData; };
New Territory Type traits aren’t all just templates and specialization, some type traits make use of compile-time info in disturbing ways… Bring on the hax!
IsConvertible Andrei Alexandrescu is a genius. Everyone should at least look at Modern C++ Design. Andrei discovered a method of inadvertently using the compiler to check if one type is convertible to another.
IsConvertible cont. template <typename To, typename From> struct IsConvertible { typedef char Yes; struct No { char a[2]; }; static Yes Test(To); static No Test(…); static From MakeFrom(); enum { value = sizeof(Test(MakeFrom())) == sizeof(Yes); }; };
IsConvertible Example template <typename T> class CRTPClass { public: CRTPClass() { CTAssert(IsConvertible<T*, CRTPClass*>::value); } };
IsConvertible Example cont. class MyClass : public CRTPClass<int> { // … }; This will now cause a compiler error from our static assert! Template type should be MyClass!
Compiler Intrinsics The latest C++ compilers support type traits intrinsics. These type traits are generally much more powerful than home-brewed traits. Some intrinsic traits include: __is_polymorphic and __has_trivial_constructor
Type Traits Are Amazing. This lecture only covered a handful of the many, many cool type traits that exist. The ones I’ve shown are some that I’ve found particularly useful. Yes. The rabbit hole DOES go deeper… Next time: SFINAE