fork(1) download
  1. // Type Functions in C++
  2. // Todd Fleming
  3. // 2014/12/01
  4.  
  5. #include <assert.h>
  6. #include <iostream>
  7. #include <type_traits>
  8. #include <typeinfo>
  9. #include <utility>
  10.  
  11. using namespace std;
  12.  
  13. // C++ metafunctions normally don't look like functions. What if
  14. // this changed with C++11/C++14? What if metafunctions can be
  15. // C++ functions? I decided to try it out.
  16. //
  17. // I decided to begin with type functions. We want functions
  18. // which take types as arguments and return types. Here's a type
  19. // container for these functions to operate on.
  20.  
  21. template<class T>
  22. struct Type
  23. {
  24. using type = T;
  25. };
  26.  
  27. // Let's try something easy. ptr() turns a T into a T*.
  28.  
  29. template<class T>
  30. auto ptr(Type<T>)
  31. {
  32. return Type<T*>{};
  33. }
  34.  
  35. // Let's use this to declare an int**.
  36.  
  37. void testPtr1()
  38. {
  39. int i{};
  40. int* p{&i};
  41. decltype(ptr(ptr(Type<int>{})))::type pp{&p};
  42.  
  43. assert(typeid(pp) == typeid(int**));
  44. }
  45.  
  46. // We only "call" ptr() within decltype(); this prevents it from
  47. // generating any runtime code, even in debug builds. This isn't
  48. // very readable, so let's simplify the interface a bit.
  49.  
  50. static const Type<int> IntType;
  51.  
  52. #define EXTRACT_TYPE(e) decltype(e)::type
  53.  
  54. void testPtr2()
  55. {
  56. int i{};
  57. int* p{&i};
  58. EXTRACT_TYPE(ptr(ptr(IntType))) pp{&p};
  59.  
  60. assert(typeid(pp) == typeid(int**));
  61. }
  62.  
  63. // We established an approach for defining and using a type
  64. // function; let's see how well this works with pattern
  65. // matching.
  66.  
  67. template<class T>
  68. auto removeCV(Type<T>)
  69. {
  70. return Type<T>{};
  71. }
  72.  
  73. template<class T>
  74. auto removeCV(Type<const T>)
  75. {
  76. return Type<T>{};
  77. }
  78.  
  79. template<class T>
  80. auto removeCV(Type<volatile T>)
  81. {
  82. return Type<T>{};
  83. }
  84.  
  85. template<class T>
  86. auto removeCV(Type<const volatile T>)
  87. {
  88. return Type<T>{};
  89. }
  90.  
  91. void testRemoveCV1()
  92. {
  93. EXTRACT_TYPE(ptr(removeCV(Type<int>{})))
  94. p1{};
  95. EXTRACT_TYPE(ptr(removeCV(Type<const int>{})))
  96. p2{};
  97. EXTRACT_TYPE(ptr(removeCV(Type<volatile int>{})))
  98. p3{};
  99. EXTRACT_TYPE(ptr(removeCV(Type<const volatile int>{})))
  100. p4{};
  101. EXTRACT_TYPE(ptr(Type<const volatile int>{}))
  102. p5{};
  103.  
  104. assert(typeid(p1) == typeid(int*));
  105. assert(typeid(p2) == typeid(int*));
  106. assert(typeid(p3) == typeid(int*));
  107. assert(typeid(p4) == typeid(int*));
  108. assert(typeid(p5) == typeid(const volatile int*));
  109. }
  110.  
  111. // That works. Let's simplify our test case a bit.
  112.  
  113. #define ASSERT_EXTRACT_TYPE(a, b) \
  114.   assert(typeid(EXTRACT_TYPE(a)) == typeid(b))
  115.  
  116. void testRemoveCV2()
  117. {
  118. ASSERT_EXTRACT_TYPE(
  119. ptr(removeCV(Type<int>{})),
  120. int*);
  121. ASSERT_EXTRACT_TYPE(
  122. ptr(removeCV(Type<const int>{})),
  123. int*);
  124. ASSERT_EXTRACT_TYPE(
  125. ptr(removeCV(Type<volatile int>{})),
  126. int*);
  127. ASSERT_EXTRACT_TYPE(
  128. ptr(removeCV(Type<const volatile int>{})),
  129. int*);
  130. ASSERT_EXTRACT_TYPE(
  131. ptr(Type<const volatile int>{}),
  132. const volatile int*);
  133. }
  134.  
  135. // Let's try recursion. We want a function which retrieves the
  136. // nth argument type from a function type.
  137.  
  138. template<int i>
  139. using int_ = integral_constant<int, i>;
  140.  
  141. // Found it
  142. template<class R, class A0, class... A>
  143. auto argn(int_<0>, Type<R(A0, A...)>)
  144. {
  145. return Type<A0>{};
  146. }
  147.  
  148. // Continue search
  149. template<int n, class R, class A0, class... A>
  150. auto argn(int_<n>, Type<R(A0, A...)>)
  151. {
  152. return argn(int_<n - 1>{}, Type<R(A...)>{});
  153. }
  154.  
  155. void testArgn1()
  156. {
  157. ASSERT_EXTRACT_TYPE(
  158. argn(int_<0>{}, Type<void(int, double, float)>{}),
  159. int);
  160. ASSERT_EXTRACT_TYPE(
  161. argn(int_<1>{}, Type<void(int, double, float)>{}),
  162. double);
  163. ASSERT_EXTRACT_TYPE(
  164. argn(int_<2>{}, Type<void(int, double, float)>{}),
  165. float);
  166. }
  167.  
  168. // argn(int_<n>{}, ...) seems a little silly; let's give it a
  169. // nicer interface.
  170.  
  171. template<int n, class T>
  172. auto argn(Type<T> t)
  173. {
  174. return argn(int_<n>{}, t);
  175. }
  176.  
  177. void testArgn2()
  178. {
  179. ASSERT_EXTRACT_TYPE(
  180. argn<0>(Type<double(int, double, float)>{}),
  181. int);
  182. ASSERT_EXTRACT_TYPE(
  183. argn<1>(Type<double(int, double, float)>{}),
  184. double);
  185. ASSERT_EXTRACT_TYPE(
  186. argn<2>(Type<double(int, double, float)>{}),
  187. float);
  188. }
  189.  
  190. // Let's try conditionals.
  191.  
  192. template<class T, class F>
  193. auto if_(true_type, T t, F)
  194. {
  195. return t;
  196. }
  197.  
  198. template<class T, class F>
  199. auto if_(false_type, T, F f)
  200. {
  201. return f;
  202. }
  203.  
  204. // demoIf's return type depends on Cond
  205. template<class Cond>
  206. auto demoIf(Cond c)
  207. {
  208. return if_(c, Type<int>{}, Type<double**>{});
  209. }
  210.  
  211. // Let's give the compiler more work to do
  212. template<class Cond1, class Cond2>
  213. auto demoNestedIf(Cond1 c1, Cond2 c2)
  214. {
  215. return if_(c1,
  216. if_(c2,
  217. Type<char>{},
  218. Type<short>{}),
  219. if_(c2,
  220. Type<int>{},
  221. Type<long>{}));
  222. }
  223.  
  224. void testIf()
  225. {
  226. ASSERT_EXTRACT_TYPE(demoIf(true_type{}), int);
  227. ASSERT_EXTRACT_TYPE(demoIf(false_type{}), double**);
  228.  
  229. ASSERT_EXTRACT_TYPE(
  230. demoNestedIf(true_type{}, true_type{}),
  231. char);
  232. ASSERT_EXTRACT_TYPE(
  233. demoNestedIf(true_type{}, false_type{}),
  234. short);
  235. ASSERT_EXTRACT_TYPE(
  236. demoNestedIf(false_type{}, true_type{}),
  237. int);
  238. ASSERT_EXTRACT_TYPE(
  239. demoNestedIf(false_type{}, false_type{}),
  240. long);
  241. }
  242.  
  243. // We could try a type list at this point, but I'm feeling a bit
  244. // ambitious. Let's try a type map.
  245.  
  246. // Each KV must be a pair. first_type and second_type must be
  247. // default-constructable and copyable. Type<...> works well as a
  248. // key or a value.
  249. template<class... KV>
  250. struct Map
  251. {
  252. };
  253.  
  254. // Match found
  255. template<class K0, class V0, class... KV>
  256. auto at(Map<pair<K0, V0>, KV...>, K0)
  257. {
  258. return V0{};
  259. }
  260.  
  261. // Continue search. Generates a compiler error if K is not
  262. // found.
  263. template<class KV0, class... KV, class K>
  264. auto at(Map<KV0, KV...>, K k)
  265. {
  266. return at(Map<KV...>{}, k);
  267. }
  268.  
  269. void testMapAt()
  270. {
  271. using M = Map<
  272. pair<int_<4>, Type<double>>,
  273. pair<Type<int*>, Type<int>>,
  274. pair<Type<int>, Type<int*>>>;
  275.  
  276. ASSERT_EXTRACT_TYPE(at(M{}, int_<4>{}), double);
  277. ASSERT_EXTRACT_TYPE(at(M{}, Type<int*>{}), int);
  278. ASSERT_EXTRACT_TYPE(at(M{}, Type<int>{}), int*);
  279. }
  280.  
  281. // erase() takes a bit more work.
  282.  
  283. template<class KV0, class... KV1>
  284. auto prepend(KV0, Map<KV1...>)
  285. {
  286. return Map<KV0, KV1...>{};
  287. }
  288.  
  289. // Key found
  290. template<class K0, class V0, class... KV>
  291. auto erase(Map<pair<K0, V0>, KV...>, K0)
  292. {
  293. return Map<KV...>{};
  294. }
  295.  
  296. // Continue search
  297. template<class KV0, class... KV, class K>
  298. auto erase(Map<KV0, KV...>, K k)
  299. {
  300. return prepend(KV0{}, erase(Map<KV...>{}, k));
  301. }
  302.  
  303. // End of map
  304. template<class K>
  305. auto erase(Map<>, K)
  306. {
  307. return Map<>{};
  308. }
  309.  
  310. void testMapErase()
  311. {
  312. using M = Map<
  313. pair<int_<4>, Type<double>>,
  314. pair<Type<int*>, Type<int>>,
  315. pair<Type<int>, Type<int*>>>;
  316.  
  317. // key not found
  318. assert(
  319. typeid(erase(M{}, int_<2>{})) ==
  320. typeid(M));
  321.  
  322. // remove row 0, 1
  323. assert(
  324. typeid(erase(erase(M{}, int_<4>{}), Type<int*>{})) ==
  325. typeid(Map<pair<Type<int>, Type<int*>>>));
  326.  
  327. // remove row 0, 2
  328. assert(
  329. typeid(erase(erase(M{}, int_<4>{}), Type<int>{})) ==
  330. typeid(Map<pair<Type<int*>, Type<int>>>));
  331.  
  332. // remove row 2, 1
  333. assert(
  334. typeid(erase(erase(M{}, Type<int>{}), Type<int*>{})) ==
  335. typeid(Map<pair<int_<4>, Type<double>>>));
  336. }
  337.  
  338. // I'll leave insert() as an exercise.
  339. //
  340. // I like how these type functions turned out; they seem a
  341. // little less messy than the template struct approach. There's
  342. // a lot more to template metaprogramming than just manipulating
  343. // types; I plan to keep exploring.
  344.  
  345. int main()
  346. {
  347. testPtr1();
  348. testPtr2();
  349. testRemoveCV1();
  350. testRemoveCV2();
  351. testArgn1();
  352. testArgn2();
  353. testIf();
  354. testMapAt();
  355. testMapErase();
  356.  
  357. cout << "Tests passed" << endl;
  358. }
  359.  
Success #stdin #stdout 0s 3340KB
stdin
Standard input is empty
stdout
Tests passed