I decided to begin with type functions. We want functions which take types as arguments and return types. Here's a type container for these functions to operate on.
template<class T> struct Type { using type = T; };Let's try something easy. ptr() turns a T into a T*.
template<class T> auto ptr(Type<T>) { return Type<T*>{}; }Let's use this to declare an int**.
void testPtr1() { int i{}; int* p{&i}; decltype(ptr(ptr(Type<int>{})))::type pp{&p}; assert(typeid(pp) == typeid(int**)); }We only "call" ptr() within decltype(); this prevents it from generating any runtime code, even in debug builds. This isn't very readable, so let's simplify the interface a bit.
static const Type<int> IntType; #define EXTRACT_TYPE(e) decltype(e)::type void testPtr2() { int i{}; int* p{&i}; EXTRACT_TYPE(ptr(ptr(IntType))) pp{&p}; assert(typeid(pp) == typeid(int**)); }We established an approach for defining and using a type function; let's see how well this works with pattern matching.
template<class T> auto removeCV(Type<T>) { return Type<T>{}; } template<class T> auto removeCV(Type<const T>) { return Type<T>{}; } template<class T> auto removeCV(Type<volatile T>) { return Type<T>{}; } template<class T> auto removeCV(Type<const volatile T>) { return Type<T>{}; } void testRemoveCV1() { EXTRACT_TYPE(ptr(removeCV(Type<int>{}))) p1{}; EXTRACT_TYPE(ptr(removeCV(Type<const int>{}))) p2{}; EXTRACT_TYPE(ptr(removeCV(Type<volatile int>{}))) p3{}; EXTRACT_TYPE(ptr(removeCV(Type<const volatile int>{}))) p4{}; EXTRACT_TYPE(ptr(Type<const volatile int>{})) p5{}; assert(typeid(p1) == typeid(int*)); assert(typeid(p2) == typeid(int*)); assert(typeid(p3) == typeid(int*)); assert(typeid(p4) == typeid(int*)); assert(typeid(p5) == typeid(const volatile int*)); }That works. Let's simplify our test case a bit.
#define ASSERT_EXTRACT_TYPE(a, b) \ assert(typeid(EXTRACT_TYPE(a)) == typeid(b)) void testRemoveCV2() { ASSERT_EXTRACT_TYPE( ptr(removeCV(Type<int>{})), int*); ASSERT_EXTRACT_TYPE( ptr(removeCV(Type<const int>{})), int*); ASSERT_EXTRACT_TYPE( ptr(removeCV(Type<volatile int>{})), int*); ASSERT_EXTRACT_TYPE( ptr(removeCV(Type<const volatile int>{})), int*); ASSERT_EXTRACT_TYPE( ptr(Type<const volatile int>{}), const volatile int*); }Let's try recursion. We want a function which retrieves the nth argument type from a function type.
template<int i> using int_ = integral_constant<int, i>; // Found it template<class R, class A0, class... A> auto argn(int_<0>, Type<R(A0, A...)>) { return Type<A0>{}; } // Continue search template<int n, class R, class A0, class... A> auto argn(int_<n>, Type<R(A0, A...)>) { return argn(int_<n - 1>{}, Type<R(A...)>{}); } void testArgn1() { ASSERT_EXTRACT_TYPE( argn(int_<0>{}, Type<void(int, double, float)>{}), int); ASSERT_EXTRACT_TYPE( argn(int_<1>{}, Type<void(int, double, float)>{}), double); ASSERT_EXTRACT_TYPE( argn(int_<2>{}, Type<void(int, double, float)>{}), float); }argn(int_<n>{}, ...) seems a little silly; let's give it a nicer interface.
template<int n, class T> auto argn(Type<T> t) { return argn(int_<n>{}, t); } void testArgn2() { ASSERT_EXTRACT_TYPE( argn<0>(Type<double(int, double, float)>{}), int); ASSERT_EXTRACT_TYPE( argn<1>(Type<double(int, double, float)>{}), double); ASSERT_EXTRACT_TYPE( argn<2>(Type<double(int, double, float)>{}), float); }Let's try conditionals.
template<class T, class F> auto if_(true_type, T t, F) { return t; } template<class T, class F> auto if_(false_type, T, F f) { return f; } // demoIf's return type depends on Cond template<class Cond> auto demoIf(Cond c) { return if_(c, Type<int>{}, Type<double**>{}); } // Let's give the compiler more work to do template<class Cond1, class Cond2> auto demoNestedIf(Cond1 c1, Cond2 c2) { return if_(c1, if_(c2, Type<char>{}, Type<short>{}), if_(c2, Type<int>{}, Type<long>{})); } void testIf() { ASSERT_EXTRACT_TYPE(demoIf(true_type{}), int); ASSERT_EXTRACT_TYPE(demoIf(false_type{}), double**); ASSERT_EXTRACT_TYPE( demoNestedIf(true_type{}, true_type{}), char); ASSERT_EXTRACT_TYPE( demoNestedIf(true_type{}, false_type{}), short); ASSERT_EXTRACT_TYPE( demoNestedIf(false_type{}, true_type{}), int); ASSERT_EXTRACT_TYPE( demoNestedIf(false_type{}, false_type{}), long); }We could try a type list at this point, but I'm feeling a bit ambitious. Let's try a type map.
// Each KV must be a pair. first_type and second_type must be // default-constructable and copyable. Type<...> works well as a // key or a value. template<class... KV> struct Map { }; // Match found template<class K0, class V0, class... KV> auto at(Map<pair<K0, V0>, KV...>, K0) { return V0{}; } // Continue search. Generates a compiler error if K is not // found. template<class KV0, class... KV, class K> auto at(Map<KV0, KV...>, K k) { return at(Map<KV...>{}, k); } void testMapAt() { using M = Map< pair<int_<4>, Type<double>>, pair<Type<int*>, Type<int>>, pair<Type<int>, Type<int*>>>; ASSERT_EXTRACT_TYPE(at(M{}, int_<4>{}), double); ASSERT_EXTRACT_TYPE(at(M{}, Type<int*>{}), int); ASSERT_EXTRACT_TYPE(at(M{}, Type<int>{}), int*); }erase() takes a bit more work.
template<class KV0, class... KV1> auto prepend(KV0, Map<KV1...>) { return Map<KV0, KV1...>{}; } // Key found template<class K0, class V0, class... KV> auto erase(Map<pair<K0, V0>, KV...>, K0) { return Map<KV...>{}; } // Continue search template<class KV0, class... KV, class K> auto erase(Map<KV0, KV...>, K k) { return prepend(KV0{}, erase(Map<KV...>{}, k)); } // End of map template<class K> auto erase(Map<>, K) { return Map<>{}; } void testMapErase() { using M = Map< pair<int_<4>, Type<double>>, pair<Type<int*>, Type<int>>, pair<Type<int>, Type<int*>>>; // key not found assert( typeid(erase(M{}, int_<2>{})) == typeid(M)); // remove row 0, 1 assert( typeid(erase(erase(M{}, int_<4>{}), Type<int*>{})) == typeid(Map<pair<Type<int>, Type<int*>>>)); // remove row 0, 2 assert( typeid(erase(erase(M{}, int_<4>{}), Type<int>{})) == typeid(Map<pair<Type<int*>, Type<int>>>)); // remove row 2, 1 assert( typeid(erase(erase(M{}, Type<int>{}), Type<int*>{})) == typeid(Map<pair<int_<4>, Type<double>>>)); }I'll leave insert() as an exercise.
I like how these type functions turned out; they seem a little less messy than the template struct approach. There's a lot more to template metaprogramming than just manipulating types; I plan to keep exploring.
#include <assert.h> #include <iostream> #include <type_traits> #include <typeinfo> #include <utility> using namespace std; ... code snippets from above ... int main() { testPtr1(); testPtr2(); testRemoveCV1(); testRemoveCV2(); testArgn1(); testArgn2(); testIf(); testMapAt(); testMapErase(); cout << "Tests passed" << endl; }Full source at ideone
No comments:
Post a Comment