检测模板模板参数的实际元数

4
我正在尝试使用模板元编程,具体来说是使用类型序列和类似STL的算法来处理这些序列。我遇到的一个问题是转换谓词,例如通过绑定它们的参数。
在提供一些背景信息之前,我认为很难描述我的问题。以下是一个例子:
#include <type_traits>

// Type sequence
template<class... T>
struct typeseq
{
    static constexpr size_t Size = sizeof...(T);
};

// Some algorithm
template<class TS, template<class...> class P>
using remove_if = /* ... some template logic ... */;

// Usage example:
using a = typeseq<int, float, char*, double*>;
using b = remove_if<a, std::is_pointer>;   // b is typeseq<int, float>

如下所示,remove_if需要一个断言函数,作为删除算法的神谕,以确定要删除哪些元素。由于我们涉及到元编程,因此其形式是模板模板参数。请注意,P 在这里使用了可变模板参数。尽管我期望是一元模板,但早期版本的 C++ 有一个限制,即可变模板参数不能用作非可变模板参数,这严重限制了谓词转换。 它要求在实例化时,解析为具有嵌套编译时 value 成员的类型,其可转换为 bool
一切都很好,但是假设您想要删除每种可以转换为 int 的类型。显然,std :: is_convertible 是一个二元谓词,但上面的 remove_if 需要一个一元谓词。我们只需要将第二个模板参数修复为 int。让我们定义一个bind2nd
template<template<class, class...> class P, class BIND>
struct bind2nd
{
    template<class T1, class... Tn> using type = P<T1, BIND, Tn...>;
};

// and now we should be able to do:
using c = remove_if<ts, bind2nd<std::is_convertible, int>::type>;   // c is typeseq<char*, double*>;

很不幸,这段代码在最新的Clang和MSVC++上无法编译。显然,问题出在将Tn...的可扩展包展开到std::is_convertible<T1, BIND, Tn...>中,而std::is_convertible只有2个模板参数。实际上,这个可扩展包为空好像并没有关系(见示例)。
我不想为传递给bind2nd的任何谓词的所需程序空间提供“重载”。有没有一种方法可以检测bind2ndP的程序空间? GCC允许我对非可变数量版本的P进行部分特化:
template<template<class, class> class P, class BIND>
struct bind2nd<P, BIND>
{
    template<class T1> using type = P<T1, BIND>;
};

但不幸的是,GCC并不是第一个抱怨的。 我也怀疑这样的部分特化有多符合规范。 有没有办法绕过这个问题? 是否可以检测模板模板参数的实际参数数量,并根据该信息执行不同的操作?

1个回答

3

我想我找到了一个解决方法。

问题似乎与类型别名有关 - 它们似乎直接传递任何模板参数,而不像类或结构体一样实例化类型。我们可以利用这一点,通过使用结构体作为中间步骤:

template<template<class, class...> class P, class BIND>
struct bind2nd
{
    template<class... Tn>
    struct impl
    {
        using type = P<Tn...>;
    };
    template<class T1, class... Tn> using type = typename impl<T1, BIND, Tn...>::type;
};

现在它可以工作了 :). 尽管有点复杂,但我想知道这是否符合标准,但它似乎可以在所有主要编译器上编译。

编辑: 我为什么要使用嵌套类型和别名来使事情复杂化?我同样可以使用谓词推导:

template<template<class, class...> class P, class BIND>
struct bind2nd
{
    template<class T1, class... Tn>
    struct type : P<T1, BIND, Tn...> { };
};

简洁明了,几乎与OP中bind2nd的第一定义完全相同。

@bolov 谢谢,这解释了很多问题。而且,我不确定为什么我如此热衷于使用别名,当我同样可以使用基类。我已经扩展了我的答案。 - oisyn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接