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:
template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
template<class...Ts>struct types : tag<types<Ts...>>{};
transform, or fmap
, then looks like:
template<template<class...>class Z, class Types>
struct fmap;
template<template<class...>class Z, class...Ts>
struct fmap<Z, types<Ts...>>:types<Z<Ts...>>{};
template<template<class...>class Z, class Types>
using fmap_t = type_t<fmap<Z,Types>>;
and you can either use type_t<fmap<Z,types<int,double>>>
, or fmap_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:
template<class T>struct tag{using type=T;};
template<class...>struct types{using type=types;};
template<class Tag>using type_t=typename Tag::type;
template<template<class...>class Z>
struct z {template<class...Ts>using apply=Z<Ts...>; constexpr z(){};};
template<class...Ts>
struct one_type {};
template<class T0>
struct one_type<T0> { using type=T0; };
template<class...Ts>
using one_type_t=typename one_type<Ts...>::type;
template<template<class>class Z>
struct z_one_base {
template<class...Ts>
using helper = Z<one_type_t<Ts...>>;
using type = z<helper>;
};
template<template<class>class Z>
using z_one = type_t<z_one_base<Z>>;
now fmap
is simply:
// take a template metafunction and a list of types
// and apply the metafunction to each type, returning the list
template<template<class...>class Z, class...Ts>
constexpr auto fmap( z<Z>, types<Ts...> )
-> types<Z<Ts>...> { return {}; }
and other functions follow:
// a template metafunction and a list of types
// and apply the template metafunction to all of the types
template<template<class...>class Z, class...Ts>
constexpr auto apply( z<Z>, types<Ts...> )
-> tag<Z<Ts...>> { return {}; }
// take any number of tags
// and make a type list from them
template<class...Tags>
constexpr auto make_list( Tags... )
-> types<type_t<Tags>...> { return {}; }
// concat of nothing is an empty list
constexpr types<> concat() { return {}; }
// concat of a list alone is a list alone:
template<class...T1s>
constexpr auto concat(types<T1s...>)
->types<T1s...>{ return {}; }
// concat of 2 or more lists is the concat of the first two,
// concatted with the rest
template<class...T1s, class...T2s, class...Types>
constexpr auto concat(types<T1s...>,types<T2s...>,Types...)
->decltype( concat(types<T1s...,T2s...>{},Types{}...) )
{ return {}; }
// take a tagged list or a tagged type, and return a list
template<class T>
constexpr auto fbox( tag<T> )->types<T> { return {}; }
template<class...Ts>
constexpr auto fbox( tag<types<Ts...>> )->types<Ts...> { return {}; }
// create z_ versions of functions above:
#define CAT2(A,B) A##B
#define CAT(A,B) CAT2(A,B)
// lift functions to metafunctions with z_ prefix:
#define Z_F(F) \
template<class...Ts> \
using CAT(meta_, F) = decltype( F( Ts{}... ) ); \
using CAT(CAT(z_, F),_t) = z<CAT(meta_, F)>; \
static constexpr CAT(CAT(z_, F),_t) CAT(z_, F){}
Z_F(concat);
//Z_F(apply);
//Z_F(fmap);
Z_F(fbox);
static constexpr z_one<tag> z_tag{};
// joins a list of lists or types into a list of types
template<class...Ts>
constexpr auto join1(types<Ts...>)
->type_t<decltype( apply( z_concat, fmap( z_fbox, types<tag<Ts>...>{} ) ) )>
{ return {}; }
template<class Types>
constexpr auto join(Types types)
->type_t<decltype( apply( z_concat, fmap( z_fbox, fmap( z_tag, types ) ) ) )>
{ return {}; }
template<class Z, class...Ts>
constexpr auto fbind(Z z, Ts...ts)
->decltype( join( fmap( z, ts... ) ) )
{ return {}; }
and work with psuedo-types (tag
s) instead of with types directly at the top level. If you need to lift back to types with type_t
when you want to.
I think this is a boost::hana
like approach, but I have only started looking at boost::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 the using
and empty-primary-specialization tricks.
Everything that is consumed is a wrapped type of tag<?>
or types<?>
or z<?>
, so nothing is "real".
Test code:
template<class T> using to_double = double;
template<class T> using to_doubles = types<double>;
int main() {
types< int, int, int > three_ints;
auto three_double = fmap( z_one<to_double>{}, three_ints );
three_double = types<double, double, double >{};
auto three_double2 = join( fmap( z_one<to_doubles>{}, three_ints ) );
three_double = three_double2;
auto three_double3 = fbind( z_one<to_doubles>{}, three_ints );
three_double3 = three_double2;
}
Live example.
typename L::template transform<F>
vs.transform<F, L>
. – Boldt