使用模板模板函数获取模板参数的数量

Get number of template parameters with template template function

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

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.

反射当然会使这变得微不足道。

°阅读本文之前Post

This post does not answer to "How to get the number of parameters",
it answers to "how to get the number of arguments"...

It is let here for two reasons:

  • It might help someone who would have mixed up (like I did)
    the meaning of parameters and arguments.
  • The techniques used in this post are closely related to the ones used
    to produce the correct answer I've posted as a separate answer.

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


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

Meanwhile, the answer of Elliott vanished...
So I've posted something similar to what he showed below.
(see "Generic Solution")

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

Note
It is a 'fancy solution', but it works...

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>>()){});

Explanation
You said "because Ts can not be referenced", which is true and false, because of the way you made the declaration of template_size.
That is, a template template parameter cannot declare parameters itself (where you placed Ts in the declaration of the function template). It is allowed to do so to give a clue of what the template argument is expected to receive as argument, but it is not a declaration of a parameter name for the current template declaration...
(I hope it's clear enough) ;)

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

通用解决方案

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;

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

有一个...

°简介

Like @Yakk pointed out in his comment to my other answer (without saying it explicitly), it is not possible to 'count' the number of parameters declared by a template. It is, on the other hand, possible to 'count' the number of arguments passed to an instantiated template.

Like my other answer shows it, it is rather easy to count these arguments.

所以...
如果不能计算参数...
实例化一个模板,而不知道该模板应该接收的参数数量怎么可能???

Note
If you wonder why the word instantiate(d) has been stricken throughout this post, you'll find its explanation in the footnote. So keep reading... ;)

°搜索过程

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

有一个...

以下是可以使之成为可能的要素:

  1. 模板class只用声明typename参数可以接收任何类型作为论据。事实上,虽然可以为特定类型定义专门化,
    主模板无法强制其参数类型.
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^

    • 从 C++20 的概念来看,上述说法可能不再成立。 我不能尝试 ATM,但@Yakk 似乎对这个问题很有信心。 他的评论后:

    I think concepts breaks this. "a primary template cannot enforce the type of its arguments." is false.

    • 如果在模板实例化之前应用约束,他 可能 是正确的。 但是...
    • 通过快速跳转到 Constraints and concepts 的介绍,可以在第一个代码示例之后阅读:

    "Violations of constraints are detected at compile time, early in the template instantiation process, which leads to easy to follow error messages."

    • 待确认...
  2. 完全可以创建一个仅用于以下目的的模板 用任意数量的参数实例化。对于我们这里的用例,它可能只包含 ints...(我们称之为 IPack)。

  3. 可以定义IPack的成员模板来定义下一个 IPack通过添加一个int 到当前 IPack 的参数。这样就可以逐步增加参数的数量...

  4. 这是可能缺失的部分。 也许大多数没有意识到

    • (我的意思是,我们大多数人在模板中经常使用它,例如,当模板访问其参数之一必须具有的成员时,或者当特征测试是否存在时特定的过载等...)

    但我认为有时以不同的方式看待问题并说:

    可能有助于找到解决方案
    • 可以声明任意类型,通过组装其他类型构建,编译器评估 可以延迟直到有效使用。
    • 因此,可以IPack 的参数注入另一个模板...
  5. 最后,使用decltypestd::declval的测试特征应该能够检测操作是否成功。 (注:最后两个都用了none)

°积木

步骤 1IPack

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.

的大小

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

步骤 2IPackInjector

An example on how the arguments of a template can be injected into another template.
It uses a template specialization to extract the arguments of an IPack,
and then, inject them into the 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 模板的声明...

Note
Have you noticed that the Victim template is not fully defined ?
It is not a complete type. It's only a forward declaration of a template.

  • The template to be tested will not need to be a complete type
    for this solution to work as expected... ;)

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

家属姓名有关。你知道这个烦人的规则 每次访问成员模板时都会强制您添加 ::template 模板的......事实上,这条规则也强制编译器不 评估包含 相关名称 的表达式,直到它是 有效使用...

  • 哦,我明白了! ...
    所以,只需要准备 IPackInjectors 没有 访问其 Type 成员,然后将其传递给我们的测试特征,对吗? 可以使用类似的东西来完成:
using TPI1 = IPackInjector<IPack1, Victim>; // No error
using TPI2 = IPackInjector<IPack2, Victim>; // No error
using TPI3 = IPackInjector<IPack3, Victim>; // No error

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

很遗憾,无法通过这些预配置 为我们的测试特征键入构建器,因为有人想使用 SFINAE 检测任意类型是否可以实例化
这又一次与 相关名称...

有关

可以利用 SFINAE 规则使编译器静默运行 select 另一个模板 (或重载) 仅当替换 模板中的参数是 相关名称 .
in clear: 仅针对当前模板实例化的一个参数。

因此,为了使检测正常工作而不产生 错误,用于测试的任意类型必须是 构建在测试特征中,至少有一个参数。 测试结果将分配给Success成员...

步骤 3TypeTestor

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,下一次测试为 false
  • 测试次数必须有限制。
    • 的确,一个模板可以有一个参数包,而且这样的模板可以接收不确定数量的参数...

步骤 4TemplateArity

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 Last, 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, P, 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;

°结论

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

在找到可以用来完成它的“工具”之后,通常只是将正确的部分放在正确的位置地方... :P

尽情享受吧!


° 重要脚注

Here is the reason why the word intantiate(d) has been stricken at the places where it was used in relation to the Victim template.

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

最好使用 trydeclare,或者 alias Victim 模板的 未来实例化 的类型。
(这会很无聊):P

事实上,Victim 模板的 none 得到 ever 实例化 在此解决方案的代码中...

作为证明,上面代码中的所有测试都仅针对模板的前向声明进行。

如果您仍有疑问...

using A = Victim<int>;      // error: "Too few arguments"
using B = Victim<int, int>; // No errors

template struct Victim<int, int>;
//              ^^^^^^^^^^^^^^^^
// Warning: "Explicit instantiation has no definition"

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

  • 有可能到'count'模板声明参数的数量...
  • 没有此模板的实例化