问题
I'm trying to write a Bind
metaprogramming template helper metafunction that binds a template parameter to something.
I have a working implementation for simple template metafunctions:
template<typename T0, typename T1>
struct MakePair
{
using type = std::pair<T0, T1>;
};
template<template<typename...> class TF, typename... Ts>
struct Bind
{
template<typename... TArgs>
using type = TF<Ts..., TArgs...>;
};
using PairWithInt = typename Bind<MakePair, int>::type;
static_assert(std::is_same<PairWithInt<float>, MakePair<int, float>>{}, "");
But what if MakePair
's template arguments were template templates? Or simple numerical values?
template<template<typename> class T0, template<typename> class T1>
struct MakePair0
{
using type = /*...*/;
};
template<template<typename...> class TF, template<typename> class... Ts>
struct Bind0 { /*...*/ }
// ...
template<int T0, int T1>
struct MakePair1
{
using type = /*...*/;
};
template<template<int...> class TF, int... Ts>
struct Bind1 { /*...*/ }
A lot of unnecessary repetition. It gets unmanageable if template arguments are mixed between types, template templates, and integral constants.
Is something like the following piece of code possible?
template<template<ANYTHING...> class TF, ANYTHING... Ts>
struct BindAnything
{
template<ANYTHING... TArgs>
using type = TF<Ts..., TArgs...>;
};
ANYTHING
would accept types, template templates, template template templates, integral values, etc...
回答1:
When I'm doing serious metaprogramming, I turn everything into types.
template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
template<template<class...>class> struct Z {};
template<class Z, class...Ts>
struct apply {};
template<template<class...>class z, class...ts>
struct apply< Z<z>, ts... >:
tag< z<ts...> >
{};
template<class Z, class...Ts>
using apply_t = type_t< apply<Z, Ts...> >;
now we pass template<?> foo
around as Z<foo>
, and it is now a type.
Similar things can be done for constants, using std::integral_constant<T, t>
(and easier to use aliases of same), or template<class T, T* p> struct pointer_constant {};
, by turning them into types.
Once everything is a type, your metaprogramming becomes more uniform. Templates just become a kind of type on which apply_t
does things to.
There is no way in C++ to have a template argument that can be a type, a value or a template. So this is about the best you can get.
templates not written for the above pattern need to be wrapped up, and their arguments "lifted" to being types. As an example:
template<class T, class t>
using number_constant = std::integral_constant< T, t{} >;
using number_constant_z = Z<number_constant>;
has had its arguments "lifted" from values to types, and then it has been wrapped with a Z
to turn itself into a type.
Bind now reads:
template<class z, class... Ts>
struct Bind {
template<class... More>
using type_base = apply_t< z, Ts..., More... >;
using type = Z<type_base>;
};
template<class Z, class...Ts>
using Bind_t = type_t<Bind<Z,Ts...>>; // strip ::type
using Bind_z = Z<Bind_t>; // quote into a Z<?>
and Bind_z
is a type wrapping a template that returns a wrapped template, and takes a type that wraps a template as its first argument.
To use it:
template<class...>struct types{using type=types;};
using types_z=Z<types>;
template<class...Ts>
using prefix =apply_t< Bind_z, types_z, Ts... >;
using prefix_z = Z<prefix>;
prefix_z
takes a set of types, and generates a factory of types<?...>
that will contain the prefix Ts...
first.
apply_t< apply_t< prefix_z, int, double, char >, std::string >
is
types< int, double, char, std::string >
live example.
There is another fun approach: do metaprogramming in functions:
template<template<class...>class z, class...Ts>
constexpr auto apply_f( Z<z>, tag<Ts>... )
-> tag<z<Ts...>> { return {}; }
here, types are represented by values of type tag<t>
, templates a Z<z>
and values as std::integral_constant<?>
.
These two:
template<class T>
constexpr tag<T> Tag = {};
template<template<class...>class z>
constexpr Z<z> Zag = {};
give you ways to get values that represent types and templates respectively.
#define TYPEOF(...) type_t<decltype(__VA_ARGS__)>
is a macro that moves from an instance of a tag
to type type in the tag, and Tag<?>
moves from a type to an instance of a tag.
TYPEOF( apply_f( apply_f( Zag<prefix>, Tag<int>, Tag<double>, Tag<char> ), Tag<std::string> ) )
is
apply_t< apply_t< prefix_z, int, double, char >, std::string >
strange, but can be interesting.
回答2:
I think you're looking for quote
and map
here. First, you want something that given a "metafunction class" and a sequence of arguments gives you a new type:
template <typename MCls, typename... Args>
using map = typename MCls::template apply<Args...>;
As the implementation here suggests, a metafunction class is one that has an member alias template named apply
.
To turn a class template into a metafunction class, we introduce quote
:
template <template <typename...> class C>
struct quote {
template <typename... Args>
using apply = C<Args...>;
};
The above is sufficient to do something like:
using T = map<quote<std::tuple>, int, char, double>;
to yield the type:
std::tuple<int, char, double>
In your example, we could write:
using P = map<quote<MakePair>, int, char>::type; // std::pair<int, char>
but I would instead prefer to make MakePair
a metafunction class directly:
struct MakePair2 {
template <typename T, typename U>
using apply = std::pair<T, U>;
};
using P = map<MakePair2, int, char>; // also std::pair<int, char>
that avoids the extra ::type
.
Consistently use the concepts of metafunction (a type with a member typedef named type
, e.g. map
) and a metafunction class (a type with a member template alias named apply
, e.g. quote
) and use only those concepts throughout your metaprogramming code. Values and class templates are second-class citizens.
来源:https://stackoverflow.com/questions/32056649/bind-metafunction-accept-both-types-and-template-template-parameters-accept-an