绑定元函数:接受类型和模板模板参数(接受任何参数)

Bind metafunction: accept both types and template template parameters (accept anything)

本文关键字:参数 任何 类型 函数 绑定      更新时间:2023-10-16

我正在尝试编写一个Bind元编程模板助手元函数,它将模板参数绑定到某些东西。

我有一个简单的模板元函数的工作实现:
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>>{}, "");

但是如果MakePair的模板参数是模板模板呢?还是简单的数值?

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 { /*...*/ }
很多不必要的重复。如果模板参数混合在类型、模板模板和整型常量之间,就会变得难以管理。

像下面这段代码是可能的吗?

template<template<ANYTHING...> class TF, ANYTHING... Ts>
struct BindAnything
{
    template<ANYTHING... TArgs>
    using type = TF<Ts..., TArgs...>;
};

ANYTHING将接受类型,模板模板,模板模板模板,整数值等

当我在做严肃的元编程时,我把所有都变成类型。

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...> >;

现在我们传递template<?> foo作为Z<foo>,它现在是一个类型。

对于常量也可以做类似的事情,使用std::integral_constant<T, t>(以及更容易使用的别名)或template<class T, T* p> struct pointer_constant {};,通过将它们转换为类型。

一旦一切都是类型,您的元编程就变得更加统一。模板只是成为一种类型的apply_t对其进行处理。

在c++中,模板实参不能同时是类型、值或模板。所以这是你能得到的最好的了。

没有为上述模式编写的

模板需要打包,并将它们的参数"提升"为类型。例如:

template<class T, class t>
using number_constant = std::integral_constant< T, t{} >;
using number_constant_z = Z<number_constant>;

将其参数从值"提升"为类型,然后用Z将其包装为类型。

Bind现在读取:

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是一个包装模板的类型,它返回一个包装模板,并接受一个包装模板的类型作为其第一个参数。

使用它:

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接受一组类型,并生成types<?...>的工厂,该工厂首先包含前缀Ts...

apply_t< apply_t< prefix_z, int, double, char >, std::string >

types< int, double, char, std::string >

生活例子。

还有另一种有趣的方法:在函数中进行元编程:

template<template<class...>class z, class...Ts>
constexpr auto apply_f( Z<z>, tag<Ts>... )
-> tag<z<Ts...>> { return {}; }

在这里,类型由类型为tag<t>的值表示,模板为Z<z>,值为std::integral_constant<?>

这两个:

template<class T>
constexpr tag<T> Tag = {};
template<template<class...>class z>
constexpr Z<z> Zag = {};

给出了获取分别代表类型和模板的值的方法。

#define TYPEOF(...) type_t<decltype(__VA_ARGS__)>

是一个宏,它从tag的实例移动到标记中的类型类型,而Tag<?>从类型移动到标记的实例。

TYPEOF( apply_f( apply_f( Zag<prefix>, Tag<int>, Tag<double>, Tag<char> ), Tag<std::string> ) )

apply_t< apply_t< prefix_z, int, double, char >, std::string >

很奇怪,但可以很有趣。

我想你在这里寻找quotemap。首先,您需要给定一个"元函数类",并通过一系列参数为您提供一个新类型:

template <typename MCls, typename... Args>
using map = typename MCls::template apply<Args...>;

正如这里的实现所示,元函数类是具有名为apply的成员别名模板的类。

要将类模板转换为元函数类,我们引入quote:

template <template <typename...> class C>
struct quote {
    template <typename... Args>
    using apply = C<Args...>;
};

上面的代码足以执行如下操作:

using T = map<quote<std::tuple>, int, char, double>;

生成类型:

std::tuple<int, char, double>

在你的例子中,我们可以这样写:

using P = map<quote<MakePair>, int, char>::type; // std::pair<int, char>

,但我宁愿让MakePair直接成为元函数类:

struct MakePair2 {
    template <typename T, typename U>
    using apply = std::pair<T, U>;
};
using P = map<MakePair2, int, char>; // also std::pair<int, char>

避免了额外的::type

始终使用元函数(具有成员类型命名为type的类型,例如map)和元函数类(具有成员模板别名命名为apply的类型,例如quote)的概念,并且在整个元编程代码中仅使用这些概念。值和类模板是二等公民。