C++ 具有 std::enable_if 的可变参数模板部分模板专用化

C++ variadic template partial template specialization with std::enable_if

本文关键字:板部 参数 专用 变参 std 具有 enable if C++      更新时间:2023-10-16

我的问题是如何在可变参数模板部分模板专用化场景中使用std::enable_if

例如,我有一个使用可变参数模板部分特化的类,如下所示

/**
*  Common case.
*/
template<typename... Args>
struct foo;
/**
*  Final superclass for foo.
*/
template<>
struct foo<>{ void func() {} };
/**
*  Regular foo class.
*/
template<typename H, typename... T>
struct foo<H, T...> : public foo<T...> {
typedef super foo<T...>;
void func() {
cout << "Hi" << endl;
this->super::template func();
}
}

它工作正常,但如果H是整数类型,我想要一个特定的部分专用化,所以我添加了如下新代码

enum class enabled {_};
template<typename H, typename... T>
struct foo<H, typename std::enable_if<std::integral_type<H>::value,enabled>::type = enabled::_, T...> : public foo<T...> {
typedef super foo<T...>;
void func() {
cout << "Hooray Inegral" << endl;
this->super::template func();
}
}

但是上面的代码不起作用,我的问题是如何像上面那样做?

enable_if<bool b, T=void>是一个类模板,用于定义type=Tifb==true.因此,只有当b==true时,enable_if<b>::type才是有效的表达式。SFINAE指出替换失败不是错误。恕我直言,not_disable_if的名字会更合适。

部分专用化的工作原理是将模板与当前解析的类型进行模式匹配。您无法在其中添加新的模板参数,因为它将匹配不同的内容。struct foo<H,std::enable_if_t<...,void>>仅匹配foo<H,void>如果...可从H中扣除并评估为true

struct foo<std::enable_if_t<std::is_integral_v<H>,H>>不能H仅仅因为无法从例如foo<int>.它必须以某种方式推导出enable_ifis_integral的语义,并看到如果H=int,那么它解析为确切的foo<int>这当然是一般无法做到的。

SFINAE只能应用于在过载解决期间考虑的那些部件。第一个和您使用的是类模板参数,但正如我上面所说,这将改变它实际匹配的内容。另一个选项是模板参数本身。但是C++不允许用于类专用化的默认模板参数,这些参数通常用于此目的。没有像函数那样的返回值。SFINAE 不使用类主体或其基类中的任何内容。我不认为你想要的是你目前的设置。

我将提供轻微的重新设计:

#include <iostream>
#include <type_traits>
// Only specifies behaviour for the head part - one T
// Ts... can be ignored, but are required.
//  - I left them because I'm not sure whether you want to use them in the real code.
//  - But they are required because `foos` use them as base classes and they must be unique.
template<typename T,bool is_integral,typename...Ts>
struct foo_impl;
template<typename T,typename...Ts>
struct foo_impl<T,true,Ts...>
{
void func() {
std::cout << "Hooray Inegral" << std::endl;
}
};
template<typename T,typename...Ts>
struct foo_impl<T,false,Ts...>
{
void func() {
std::cout << "Hi" << std::endl;
}
};
template<typename T,typename...Ts>
using foo = foo_impl<T,std::is_integral<T>::value,Ts...>;
template<typename...Ts>
struct foos;
template<typename H,typename...Ts>
struct foos<H,Ts...> : private foo<H,Ts...>, public foos<Ts...>
{
using head = foo<H,Ts...>;
using tail = foos<Ts...>;
//Only named differently for clarity, feel free to rename it back to 'func'
void rec_func()
{
//If we inherited from foo<H> so this was only this->foo<H>::func() then
//foos<int,int> would have two foo<int> base classes and this call would be ambigious.
this->head::func();
this->tail::rec_func();
}
};
template<> struct foos<>{
void rec_func(){}
};
int main()
{
foos<int,int,char,double> x;
x.rec_func();
}

foo只处理一个T,并且需要专业化。您可以将foo<T,false>foo<T,true>之间的任何常见行为提取到公共基类中。目前,foofoosAPI 之间存在重复。但是fooAPI 可以被视为私有并且可以不同,所以我根本不会说这是缺点。正如我在foo_impl中解释Ts...是必需的。如果你不需要它们,可以通过以下方式删除它们 - 例如,通过简单地从std::tuple<foo<Ts>...>和一些折叠表达式(C++17)/magic(c++14)派生来调用func函数。如果你愿意,我可以补充一下。