使用模板模板函数 通用解决方案

问题描述

我不确定这是否可行,但我想计算任何类的模板参数的数量,例如:

template <typename T>
class MyTemplateClass { ... };

template <typename T,typename U>
class MyTemplateClass2 { ... };

这样 template_size<MyTemplateClass>() == 1template_size<MyTemplateClass2>() == 2。我是模板模板的初学者,所以我想出了这个当然不起作用的功能

template <template <typename... Ts> class T>
constexpr size_t template_size() {
     return sizeof...(Ts);
}

因为 Ts 不能被引用。我也知道在处理变体模板时可能会出现问题,但事实并非如此,至少对于我的应用程序而言。

提前谢谢

解决方法

#include <utility>
#include <iostream>

template<template<class...>class>
struct ztag_t {};
    
template <template<class>class T>
constexpr std::size_t template_size_helper(ztag_t<T>) {
     return 1;
}
template <template<class,class>class T>
constexpr std::size_t template_size_helper(ztag_t<T>) {
     return 2;
}


template <typename T>
class MyTemplateClass {  };

template <typename T,typename U>
class MyTemplateClass2 {  };

template<template<class...>class T>
struct template_size:
  std::integral_constant<
    std::size_t,template_size_helper(ztag_t<T>{})
  >
{};


int main() {
    std::cout << template_size<MyTemplateClass>::value << "\n";
    std::cout << template_size<MyTemplateClass2>::value << "\n";
}

如果不写出 N 个重载来支持最多 N 个参数,我知道没有办法。

Live example

当然,反思会让这件事变得微不足道。

,

有一个...

°介绍

就像@Yakk 在他对我的另一个答案的评论中指出的(没有明确说明),不可能“计算”参数的数量 由模板声明。另一方面,可以“计算”参数传递到实例化模板的数量。

就像我的另一个答案所显示的那样,计算这些参数相当容易。

所以...如果不能计算参数...

如何在不知道的情况下实例化模板
这个模板应该接收的参数数量???

注意
如果你想知道为什么 instantiate(d) 这个词在这篇博文中出现了,
你会在脚注中找到它的解释。 所以继续阅读...;)

°搜索过程

  • 如果可以设法以某种方式尝试实例化一个带有越来越多参数的模板,然后使用 SFINAE (替换失败)检测它何时失败不是错误),应该可以找到解决此问题的通用解决方案...你不觉得吗?
  • 显然,如果想要能够也管理非类型参数,它已经死了
  • 但是对于具有 typename 参数...
    的模板
    有一个...

以下是人们应该能够使其成为可能的要素:

  1. 使用typename参数声明的模板类可以接收 任何类型作为参数。实际上,虽然可以为特定类型定义专门化,
    主模板不能强制其参数的类型
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
    >(不幸的是,从 C++20 概念来看,这种说法不再正确。)
    >(我无法尝试 ATM,但 @Yakk 似乎对这个主题相当有信心。)
    >(看来他是对的。约束,即,会使这个解决方案无法使用。)
  2. 完全可以创建一个仅用于以下目的的模板 用任意数量的参数实例化。对于我们这里的用例,它可能只包含 ints...(我们称之为 IPack)。
  3. 可以定义 IPack 的成员模板来定义 Next IPack,方法是将 int 添加到当前 {{1 }}。这样就可以逐步增加其参数的数量...
  4. 这是也许缺失的部分。 也许大多数没有意识到
    (我的意思是,我们大多数人在模板中经常使用它,例如,当模板访问其参数之一必须具有的成员时,或者当特征测试特定重载的存在等时。 .)
    但我认为有时以不同的方式看待它并说:
    • 可以声明任意类型,通过组装其他类型构建,编译器对这些类型的评估可以延迟直到它得到了有效利用。
    • 因此,可以将 IPack 的参数注入到另一个模板中...
  5. 最后,应该能够通过使用 IPackdecltype 的测试特征来检测操作是否成功。 (注意:最后两个都没用过)

° 积木

第 1 步std::declval

IPack

现在,有一种方法可以增加参数的数量,
使用一种方便的方法来检索 template<typename...Ts> struct IPack { private: template<typename U> struct Add1 {}; template<typename...Us> struct Add1<IPack<Us...>> { using Type = IPack<Us...,int>; }; public: using Next = typename Add1<IPack<Ts...>>::Type; static constexpr std::size_t Size = sizeof...(Ts); }; using IPack0 = IPack<>; using IPack1 = typename IPack0::Next; using IPack2 = typename IPack1::Next; using IPack3 = typename IPack2::Next; constexpr std::size_t tp3Size = IPack3::Size; // 3 的大小。

接下来,需要一些东西来构建任意类型
通过将 IPack 的参数注入到另一个模板中。

第 2 步IPack

关于如何将模板的参数注入另一个模板的示例。
它使用模板特化来提取 IPackInjector,
然后,将它们注入 IPack

Target

现在,有一种方法可以注入 template<typename P,template <typename...> class Target> struct IPackInjector { using Type = void; }; template<typename...Ts,template <typename...> class Target> struct IPackInjector<IPack<Ts...>,Target> { using Type = Target<Ts...>; }; template<typename T,typename U> struct Victim; template<typename P,template <typename...> class Target> using IPInj = IPackInjector<P,Target>; //using V1 = typename IPInj<IPack1,Victim>::Type; // error: "Too few arguments" using V2 = typename IPInj<IPack2,Victim>::Type; // Victim<int,int> //using V3 = typename IPInj<IPack3,Victim>::Type; // error: "Too many arguments" IPack 模板中,但是,正如人们所见,评估 Victim 如果参数数量不正确,则直接生成错误 匹配 Type 模板的声明...

注意
您是否注意到 Victim 模板没有完全定义?
它不是一个完整的类型。这只是模板的前向声明。

  • 要测试的模板不需要是完整的类型
    为了使此解决方案按预期工作... ;)

如果希望能够将这种任意构建的类型传递给某个检测特征,则必须找到一种方法来延迟其评估。 事实证明,'trick' (如果有人可以说)相当简单。

它与从属名称有关。你知道这个烦人的规则 强制您在每次访问成员模板时添加 Victim 一个模板...实际上,这个规则也强制编译器不要 评估包含依赖名称的表达式,直到它 有效利用...

  • 哦,我明白了! ...
    因此,只需要准备 ::template 而不需要 访问它的 IPackInjector 成员,然后将其传递给我们的测试特征,对吗? 可以使用类似的方法来完成:
Type

确实,上面的例子并没有产生错误,而且证实了 有一种方法可以准备要构建和评估的类型 稍后他们。

不幸的是,将无法通过这些预先配置的 为我们的测试特征键入构建器,因为人们想要使用 SFINAE 检测任意类型是否可以实例化
这再次与从属名称...

有关

可以利用 SFINAE 规则使编译器静默 仅当替换时才选择另一个模板(或重载) 模板中参数的依赖名称
明确:仅针对当前模板实例化的参数。

因此,为了使检测正常工作而不会产生 错误,用于测试的任意类型必须是 在测试特征中构建,至少具有其参数之一。 测试结果将分配给using TPI1 = IPackInjector<IPack1,Victim>; // No error using TPI2 = IPackInjector<IPack2,Victim>; // No error using TPI3 = IPackInjector<IPack3,Victim>; // No error 成员...

第 3 步Success

TypeTestor

现在,最后,需要一种能够连续尝试的机器 实例化
我们的 template<typename T,template <typename...> class C> struct TypeTestor {}; template<typename...Ts,template <typename...> class C> struct TypeTestor<IPack<Ts...>,C> { private: template<template <typename...> class D,typename V = D<Ts...>> static constexpr bool Test(int) { return true; } template<template <typename...> class D> static constexpr bool Test(...) { return false; } public: static constexpr bool Success = Test<C>(42); }; 模板具有越来越多的参数。
有几点需要注意:

  • 不能不带参数声明模板,但它可以:
    • 只有一个参数包,或者,
    • 默认所有参数。
  • 如果测试过程以失败开始,则意味着模板必须采用更多参数。所以,测试必须继续直到成功,然后,
    继续直到第一次失败。
    • 这可能会使使用模板特化的迭代算法变得有点复杂...
      但是想了想,启动条件并不重要。
      只需检测上一次测试是 Victim 的时间,下一次测试将是 true
  • 测试次数必须有限制。
    事实上,一个模板可以有一个参数包。
    这样的模板可以接收不确定数量的参数......

第 4 步false

TemplateArity

°结论

最后,解决问题的算法并没有那么复杂...

在找到可以执行此操作的“工具”后,
通常,这只是将正确的部分放在正确的位置的问题...:P

享受吧!


°重要脚注

这就是为什么 intantiate(d) 这个词在与 template<template <typename...> class C,std::size_t Limit = 32> struct TemplateArity { private: template<typename P> using TST = TypeTestor<P,C>; template<std::size_t I,typename P,bool Last,bool Next> struct CheckNext { using PN = typename P::Next; static constexpr std::size_t Count = CheckNext<I - 1,PN,TST<P>::Success,TST<PN>::Success>::Count; }; template<typename P,bool Next> struct CheckNext<0,P,Last,Next> { static constexpr std::size_t Count = Limit; }; template<std::size_t I,typename P> struct CheckNext<I,true,false> { static constexpr std::size_t Count = (P::Size - 1); }; public: static constexpr std::size_t Max = Limit; static constexpr std::size_t Value = CheckNext<Max,IPack<>,false,false>::Count; }; template<typename T = int,typename U = short,typename V = long> struct Defaulted; template<typename T,typename...Ts> struct ParamPack; constexpr std::size_t size1 = TemplateArity<Victim>::Value; // 2 constexpr std::size_t size2 = TemplateArity<Defaulted>::Value; // 3 constexpr std::size_t size3 = TemplateArity<ParamPack>::Value; // 32 -> TemplateArity<ParamPack>::Max; 模板相关的地方被使用的原因。

instantiate(d)这个词只是不是正确的词...

最好使用try声明,或者别名类型
Victim 模板的未来实例化
(这会非常无聊):P

确实,没有 Victim 模板在此解决方案的代码中实例化
作为证明,应该足以看到在上面的代码中进行的所有测试,
仅在模板的前向声明...

如果你仍然有疑问...

Victim

最后有一句完整的介绍可能是stricken,
因为这个解决方案似乎表明:

  • 可以“计算”模板声明的参数的数量...
  • 没有实例化这个模板。
,

° 阅读本文前

这篇文章没有回答“如何获得参数数量”,
它回答“如何获得参数的数量”...

放在这里有两个原因:

  • 它可能会帮助那些会混淆的人(就像我一样)
    参数参数的含义。
  • 本文中使用的技术与使用的技术密切相关
    为了生成正确答案,我已将其作为单独的答案发布。

请参阅我的其他答案以查找模板的“参数数量”。


Elliott 的答案看起来更像是人们通常所做的(尽管主模板应该完全定义并且“做某事”恕我直言)。当模板作为参数传递给主模板时,它使用模板特化。

与此同时,Elliott 的回答消失了......
所以我发布了类似于他在下面展示的内容。
(参见“通用解决方案”)

但是,只是为了向您展示您离可行的解决方案不远,因为我注意到您使用了一个函数为了您的尝试,,您声明了这个函数constexpr,您可以这样写:

注意
这是一个'花哨的解决方案',但它有效......

template <typename T>             class MyTemplateClass {};
template <typename T,typename U> class MyTemplateClass2 {};

template <template <typename...> class T,typename...Ts>
constexpr const size_t template_size(T<Ts...> && v)
{
    return sizeof...(Ts);
}

// If the target templates are complete and default constructible.
constexpr size_t size1 = template_size(MyTemplateClass<int>{});
constexpr size_t size2 = template_size(MyTemplateClass2<int,short>{});

// If the target templates are complete but NOT default constructible.
constexpr size_t size3 = template_size(decltype(std::declval<MyTemplateClass<int>>()){});
constexpr size_t size4 = template_size(decltype(std::declval<MyTemplateClass2<int,short>>()){});

说明
你说“因为Ts不能被引用”,这是,因为你声明的方式template_size
也就是说,模板模板参数不能声明参数本身(您在函数模板声明中放置 Ts 的位置)。允许这样做以提供模板参数预期作为参数接收的线索,但它不是当前模板声明的参数名称的声明...
(我希望它足够清楚) ;)

显然,它可能有点过于复杂,但值得知道的是,我认为这样的构造也是可能的...;)

通用解决方案

template <typename T>             class MyTemplateClass {};
template <typename T,typename U> class MyTemplateClass2 {};

template<typename T>
struct NbParams { static constexpr std::size_t Value = 0; };

template<template <typename...> class C,typename...Ts>
struct NbParams<C<Ts...>> { static constexpr std::size_t Value = sizeof...(Ts); };

constexpr size_t size1 = NbParams<MyTemplateClass<int>>::Value;
constexpr size_t size2 = NbParams<MyTemplateClass2<int,short>>::Value;

这是人们做这种事情的常规方式...;)