Compile-time map on a type list

327 Views Asked by At

I am looking for an idiomatic way to apply a type-level transform for each element in a type list. So far I came up with the following, which works as expected:

namespace impl_
{
template <template <typename> typename, typename>
struct MapVariant;
template <template <typename> typename F, typename... Ts>
struct MapVariant<F, std::variant<Ts...>>
{
    using Result = std::variant<F<Ts>...>;
};
}

template <typename Variant, template <typename> typename Transform>
using MapVariant = typename impl_::MapVariant<Transform, Variant>::Result;

Test:

using TestVariant = std::variant<int, char, std::array<float, 0>>;

template <typename Arg>
using TypeTransform = std::array<Arg, 0>;

static_assert(
    std::is_same_v<MapVariant<TestVariant, TypeTransform>,                                             //
                   std::variant<std::array<int, 0>, std::array<char, 0>, std::array<std::array<float, 0>, 0>>  //
                   >);

Now I would like to generalize MapVariant to accept an arbitrary container parameterized with a type list rather than only std::variant (e.g., std::tuple). My naive solution does not work:

template <template <typename...> class C, template <typename> typename F, typename... Ts>
using MapVariant = C<F<Ts>...>;

Here is a more verbose approach closer to the original that is also dysfunctional:

namespace impl_
{
template <template <typename> typename, typename, template <typename...> class, typename>
struct MapVariant;
template <template <typename> typename F, typename Unused, template <typename...> class C, typename... Ts>
struct MapVariant<F, Unused, C, C<Ts...>>
{
    using Result = C<F<Ts>...>;
};
}
template <template <typename...> class Variant, template <typename> typename Transform, typename... Ts>
using MapVariant = typename impl_::MapVariant<Transform, Variant<Ts...>, Variant, Ts...>::Result;

Clang says "Template argument for template template parameter must be a class template or type alias template". I understand that the compiler is unable to unpack TestVariant because apparently some type information is lost at the time of its definition via using, but I don't understand how to work around this.

It is possible to do this at all?

2

There are 2 best solutions below

0
Pavel Kirienko On BEST ANSWER

Thanks to Fureeish I managed to fix this with a trivial change:

template <template <typename> typename, typename>
struct MapVariant;
template <template <typename...> class C, template <typename> typename F, typename... Ts>
struct MapVariant<F, C<Ts...>>
{
    using Result = C<F<Ts>...>;
};

The outer alias need not be changed. The critical difference is that the order of template parameters was incorrect -- I would have noticed that if I just paid more attention to it. Smh. Thanks again, Fureeish.

2
Yakk - Adam Nevraumont On

I'd do this with functions rather than templates. Cleaner.

template<class T>struct tag_t{using type=T;};
template<class T>constexpr tag_t<T> tag{};
template<template<class...>class Z>
struct ztag_t{
  template<class...Ts>using result=Z<Ts...>;
  template<class...Ts>
  constexpr auto operator()(tag_t<Ts>...)const{ return tag<result<Ts...>>; }
};
template<template<class...>class Z>
constexpr ztag_t<Z> ztag{};
template<class Tag>
using type_t=typename Tag::type;
#define TYPE_T(...) type_t<decltype(__VA_ARGS__)>

template <template<class...>class Z,class...Ts>
constexpr auto fmap(auto f, tag_t<Z<Ts...>>){
  return f(tag<Ts>...);
};

Use:

using bob=std::tuple<int,double,char>;
using as_var=TYPE_T(fmap(ztag<std::variant>, tag<bob>));

ain't that pretty.

Probably has typos.