【问题标题】:Bind metafunction: accept both types and template template parameters (accept anything)绑定元函数:接受类型和模板模板参数(接受任何东西)
【发布时间】:2015-11-10 11:01:58
【问题描述】:

我正在尝试编写一个 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 将接受类型、模板模板、模板模板模板、整数值等...

【问题讨论】:

  • 这样的东西比简单地写出模板有什么好处?无论如何,这都是静态/编译时间。
  • 作文。查看我刚刚编写的这个用例,这是我在编写一些代码时遇到的。 Example pastie。 (tl;博士:Rename&lt;Map&lt;Wrapper, TList&gt;, Bind&lt;AllTrue, TPredicate&gt;&gt;

标签: c++ templates metaprogramming c++14 template-templates


【解决方案1】:

当我进行严肃的元编程时,我会将一切变成类型。

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&lt;?&gt; foo 传递为Z&lt;foo&gt;,它现在是一个类型。

可以使用std::integral_constant&lt;T, t&gt;(并且更容易使用相同的别名)或template&lt;class T, T* p&gt; 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 包装以将其自身转变为类型。

现在绑定如下:

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<?>

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&lt;?...&gt; 的工厂,其中首先包含前缀 Ts...

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

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

live example.

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

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

在这里,类型由tag&lt;t&gt; 类型的值表示,模板为Z&lt;z&gt;,值由std::integral_constant&lt;?&gt; 表示。

这两个:

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&lt;?&gt; 从类型移动到标记的实例。

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 >

很奇怪,但可能很有趣。

【讨论】:

  • “在 C++ 中没有办法让模板参数可以是类型、值或模板。” 重载函数模板可以通过一个模板参数来做到这一点.不过,这需要decltype 混乱。
  • @dyp true,我们可以采用增强 hana 风格,将元编程作为 constexpr 函数进行。但即使在那里,拥有Z 也很有用,因为它允许您将模板作为值传递。您谈论的重载解决方案最终仍然呈指数爆炸-您如何编写将模板作为其第一个模板参数的函数重载,其每个参数(传递给函数的模板)都可以是模板, size_t ,一个int,一个指向函数的指针,还是一个类型?你真的不能……
  • 广告指数爆炸:这就是我说“一个模板参数”的原因 :) 它可以用来写一个通用适配器:decltype(adapt&lt;X&gt;()),其中X 可以是类型、模板或值(或一组固定签名的函数,但没有重载集)。但由于decltype 的混乱,最好编写三个单独命名的适配器。
  • @dyp 假设 Xtemplate&lt;size_t, char, std::string*&gt; 另一个 Xtemplate&lt;class, class, class&gt;。另一个是template&lt;class T, T t&gt;template&lt;?...&gt;签名的数量是无限多的,没有办法统一谈论它们传递给adapt。你必须把你的模板变成统一的template&lt;class...&gt;。我承认#define ADAPT(...) decltype( adapt&lt;__VA_ARGS__&gt;()) 的宏可以使常量和类型的引用更加统一(甚至template&lt;class...&gt;)。有趣的是,这让hana 风格更具诱惑力,因为这会杀死decltype
  • 一旦我们得到 constexpr lambdas,使用对象甚至可能成为一种语法上令人愉悦的方法:melpon.org/wandbox/permlink/Wm9vwW3qv8JLSApZ -- 等等,我认为我们甚至不需要 constexpr lambdas。整个计算处于未评估的上下文中。
【解决方案2】:

我认为您正在这里寻找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 的成员typedef 的类型,例如map)和元函数类(具有成员的类型模板别名命名为apply,例如quote)并在整个元编程代码中仅使用这些概念。值和类模板是二等公民。

【讨论】:

  • 所以,我的Z 等同于quote,我的apply_t 等同于您的map,但可能您使用的是更标准的名称(是maphaskell 的fmap 用于monads ? 还是&gt;&gt;=?)。不同之处在于您的map 依赖于现有的T::apply&lt;?...&gt;,而我的依赖于使用Z(并且只有Z)来包装模板(除非我升级我的apply_t 以也检测T::apply&lt;?...&gt;
  • @Yakk 主要是。我想主要区别是 map 适用于任何元函数类,而 apply_t 仅适用于 Zs。
  • @Yakk Haskell 多年来一直在我的“总有一天,我会学习这个”列表中,但基本上是 fmap。我不完全确定 &gt;&gt;= 做了什么,所以不能说是这样或那样。
  • nod,它使直接元函数更容易一些,而不必执行我的系统所需的 3 个步骤(首先,生成类型的结构,然后type_t 未包装,然后是 Z 包装器)。我不喜欢 map 的名字,但是:对我来说,map&lt; Z&lt;?&gt;, list&lt;a,b,c&gt; &gt; 应该是 list&lt;Z&lt;a&gt;, Z&lt;b&gt;, Z&lt;c&gt;&gt;,而不是 Z&lt; list&lt;a,b,c&gt; &gt;
  • @Yakk apply 可能是一个更好的名字。但我坚持使用quote 而不是Z :-P
猜你喜欢
  • 2013-02-14
  • 2021-03-29
  • 2018-01-29
  • 2021-10-18
  • 1970-01-01
  • 2023-04-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-19
相关资源
最近更新 更多