我如何要求模板参数是特定模板类的特化?

时间:2021-07-04 18:23:54

标签: c++ templates variadic-templates template-specialization c++-concepts

我想定义一个模板类 MyClass,其中模板参数必须是特定模板类的特化。在这个例子中,我希望 MyClass 是一个模板类,它的第一个模板参数接受 A 的特化,第二个模板参数接受 B 的特化。然后我可以按如下方式实例化这个类:

MyClass<A<int, int>, B<double, int>>;

现在,您可能会说,“只需将 AB 的参数作为 MyClass 的模板参数”。不幸的是,在我的情况下,AB 都是可变参数模板,因此在 MyClass 的参数列表中不可能找到 A 的模板参数的结尾{1}} 和 B 的模板参数的开头。

AB 的示例如下:

template <typename... Args>
class A{
public:
    A(){}
};
template <typename... Args>
class B{
public:
    B(){}
};

我发现 older answers 可以提供使用 SFINAE 等解决方案。我的理解是,不幸的是,SFINAE 有一些缺点,比如禁用类型推导;我倾向于避免它。我的问题是:是否有更新的语言功能可以让我在没有 SFINAE 的情况下获得我想要的行为?

为了让您更好地了解我对 MyClass 的想象,我在下面提供了这个糟糕示例:

template <template<typename... Args> class A, typename... Args1, template<typename... Args> class B, typename... Args2>
class MyClass { // use A<Args1...> and B<Args2...> in here
public:
    MyClass() {}
}; // fails because template parameter pack Args1 is not at the end of the parameter list

2 个答案:

答案 0 :(得分:2)

SFINAE 的替代方案是 C++20 constraints and concepts。在您的情况下 - 无论 SFINAE 或概念如何 - key problem will be the template parameter packs,如 the standard dictates

<块引用>

函数模板的模板形参包后面不能跟另一个模板形参,除非该模板形参可以从函数模板的形参类型列表([dcl.fct])中推导出来或者有一个默认实参( [温度扣除])。没有默认参数的演绎指南模板([temp.deduct.guide])的模板参数应从演绎指南模板的参数类型列表中推导出。

这意味着你不能有类似的东西

template <template<typename...> class A, typename... Args1, template<typename...> class B, typename... Args2>
class MyClass {
  public:
    MyClass() {}
};

直接。

  • 一种可能的解决方案是根本没有模板模板类并确保隐式(例如通过对正确重载的函数的函数调用)模板参数是正确的。您实际上可以使用概念将@cigien 的方法形式化,并通过使用 std::declval 进一步消除对默认可构造性的要求:

    介绍两个只能被AB类型的对象调用的函数:

    template <typename... Args>
    void a(A<Args...>) {}
    
    template <typename... Args>
    void b(B<Args...>) {}
    

    定义两个概念,它们要求相应函数的重载存在,可以通过使用 std::declval 而不是默认构造函数来调用相应类型的值:

    template <typename T>
    concept is_A = requires { a(std::declval<T>()); };
    
    template <typename T>
    concept is_B = requires { b(std::declval<T>()); };
    

    最后在你的模板参数上强制执行相应的概念,如下所示:

    template <is_A A, is_B B>
    class MyClass {
    public:
      MyClass() {}
    };
    

    通过这种方法,类可以默认构造为

    MyClass<A<int,int>, B<double,int>> my_class{};
    

    与@cigien 的版本相反,这个不需要 AB 是默认构造的

    Try it here


根据您的计划用途,您还可以在调用构造函数时使用模板参数推导,但请注意,类本身不会区分不同的可变参数模板参数(即 {{1} } 实际上会为两个不同的参数包返回 true),只有构造函数会。此外,该类将不再是默认可构造的,您必须使用

调用它
std::is_same
  • 一种方法是根本不模板化类,而是使用两个模板参数包模板化构造函数

    MyClass my_class{A<int,int>{}, B<int,double>{}};
    

    Try it here

  • 你可以通过引入概念来使这更灵活

    class MyClass {
      public:
        template <typename... Args1, typename... Args2>
        MyClass(A<Args1...>, B<Args2...>) {}
    };
    

    (或者用 template <template<typename...> class T, typename... Ts> concept is_A = std::is_same_v<A<Ts...>,T<Ts...>>; template <template<typename...> class T, typename... Ts> concept is_B = std::is_same_v<B<Ts...>,T<Ts...>>; 代替),在不指定可变参数模板参数的情况下对类进行模板化,然后使用可以从输入参数中推导出来的两个模板参数包使构造函数模板化

    std::is_base_of

    并向类和/或构造函数添加一个 requires 子句。

    Try it here

给定的约束当然也可以用 SFINAE std::enable_if_t 而不是概念来强制执行。有关此示例,请参见例如here

答案 1 :(得分:1)

我认为通过专门化类模板是不可能的,但是您可以通过使用函数模板参数推导来解决这个问题,它可以区分多个参数包

template <typename... Args1, typename... Args2>
void f(A<Args1...>, B<Args2...>) {}

当传递 AB 的实例化时,将匹配此函数,我们将其用作真实情况。

对于错误的情况,我们可以匹配任何内容并删除它

template <typename... Ts>
void f(Ts...) = delete;

现在,您可以让 MyClass 接受 2 个类型参数,并在构造函数中调用 f,以确保参数是 AB 的实例化.

template<typename T, typename U>
class MyClass { 
public:
    MyClass() 
    {
        f(T{}, U{});  // hard error if not called with specializations of A and B
    }
};

这基本上会断言类型满足您想要的约束,但如果您也想使用它们,您可以进一步提取 AB 的参数。

这是一个 demo