I see two possible styles for implementing type lists in C++11/14 and I was curious if there's any reason to prefer one over the other. The first technique is outlined here and models itself on Boost's MPL library. In this style you define meta 'free functions' (top level using declarations) that take in type lists and operate on them. Here is how you would implement a meta version of std::transform that works on types instead of values in the first style:
template <typename... Args>
struct type_list;
namespace impl
{
template <template <typename...> class F, class L>
struct transform_impl;
template <template <typename...> class F, template <typename...> class L, typename... T>
struct transform_impl<F, L<T...>>
{
using type = L<typename F<T>::type...>;
};
}
template <template <typename...> class F, class L>
using transform = typename impl::transform_impl<F, L>::type;
The second style is to define meta 'methods' (using declarations inside the type list struct). Here is how transform looks in that style:
template <typename... Args>
struct type_list {
// ... other 'methods'
template<template<class> class Wrapper>
using transform =
type_list<Wrapper<Args>...>;
// ... other 'methods'
};
The advantage I see in the second style is that you still have the Args...
parameter pack available, so you don't have to delegate to impl
helper functions. Two possible disadvantages are that 1) you have to put all your meta functions inside type_list rather than possibly putting them in separate headers, so you lose some modularity and 2) the 'free' meta functions will also work on tuples and any other variadic template class out of the box. I don't know how common the desire for #2 actually is in practice, I have only found occasions to use type_list and tuple myself, and writing meta code to translate between type_list and tuple is not that difficult.
Is there any good reason to strongly prefer one or the other? Maybe #2 is actually a common case?
The 2nd one is bad for many reasons.
First, calling it is a mess. Templates inside templates require using
template
keyword.Second, it requires that your type list include every operation you want to do on type lists within its body. It is like defining every operation on a
string
as a method on the string: if you allow for free functions, new operations can be created, and you can even implement overrides.Finally, consider hiding the
::type
:Start with these primitives:
transform, or
fmap
, then looks like:and you can either use
type_t<fmap<Z,types<int,double>>>
, orfmap_t<Z,types<int,double>>
to get the types of the mapped-to type.Yet another approach is to use
constexpr
functions that contain various things:now
fmap
is simply:and other functions follow:
and work with psuedo-types (
tag
s) instead of with types directly at the top level. If you need to lift back to types withtype_t
when you want to.I think this is a
boost::hana
like approach, but I have only started looking atboost::hana
. The advantage here is that we decouple the type bundles from the operations, we gain access to full C++ overloading (instead of template pattern matching, which can be more fragile), and we get to directly deduce the contents of the type bundles without having to do theusing
and empty-primary-specialization tricks.Everything that is consumed is a wrapped type of
tag<?>
ortypes<?>
orz<?>
, so nothing is "real".Test code:
Live example.