带有模板参数的模板专业化
Template Specialisation with Template Argument
让我们假设有一个template
类Foo
:
template <typename T>
class Foo {
void foo();
};
我有另一个template
类Bar
(独立于第一个):
template <int N>
class Bar {};
比方说,我想专门针对任何Bar
类使用foo()
方法。我会写错:
template <>
template <int N>
void Foo<Bar<N> >::foo() { /* ... */ }
编译器指责我是因为类型不完整:
error: invalid use of incomplete type 'class Foo<Bar<N> >'
void Foo<Bar<N> >::foo() { }
代码
我使用的是C++98,但我想知道C++11中是否存在不同的解决方案。
注意
我可以解决将整个类Foo
专门化为泛型Bar
的问题,但之后我必须定义所有方法。
示例代码
这不是我想要的,我正在寻找(如果存在的话)更优雅的解决方案(C++98和C++11),它允许我专门化并只实现一个类方法。
编辑:
关于SO的问题并没有解释如何专门处理模板论点。事实上,我的问题表明编译器是如何抱怨的。
对于C++11,您可以在非专用的Foo
类中启用/禁用(使用std::enable_if
)foo()
的两个不同版本。
在C++98中,你没有抱歉:我的想法不起作用,因为这个方法需要为C++11的创新方法使用默认的模板参数。std::enable_if
,但你可以模拟它(给我几分钟时间,我试着提出一个例子)
另一种方法是为Foo()
定义一个模板基类,比如FooBase
,在FooBase
中插入foo()
(并且仅插入foo()
),并专门化FooBase
。
另一种同样适用于C++98的方法可以是标记调度:您可以定义一个唯一的foo()
,它的参数为零,它调用另一个foo()
,参数由T
确定。
以下是完整的(C++98可编译)示例
#include <iostream>
struct barWay {};
struct noBarWay {};
template <int>
struct Bar
{ };
template <typename>
struct selectType
{ typedef noBarWay type; };
template <int N>
struct selectType< Bar<N> >
{ typedef barWay type; };
template <typename T>
struct Foo
{
void foo (noBarWay const &)
{ std::cout << "not Bar version" << std::endl; }
void foo (barWay const &)
{ std::cout << "Bar version" << std::endl; }
void foo ()
{ foo(typename selectType<T>::type()); }
};
int main ()
{
Foo<int> fi;
Foo< Bar<42> > fb;
fi.foo();
fb.foo();
}
如果不需要公共基础,另一种方法可能是给foo()一个自定义点,比如一个特性:
template <typename T>
struct foo_traits;
template <typename T>
struct Foo {
void foo(){ foo_traits<T>::foo_cp(*this); }
};
template <typename T>
struct foo_traits{ static void foo_cp(T&){/*default*/} };
template <int N>
class Bar {};
template <int N>
struct foo_traits<Bar<N>>{ static void foo_cp(Foo<Bar<N>>&){/*spec*/} };
如果它的唯一目的是在内部为Bar提供foo()专门化,那么这种特性也可能是实现细节的朋友。
如果不能专门化foo
,请定义它,使其将调用委托给内部foo实现类。那就把这门课专门化
类似的东西应该在C++98中编译,它与您的原始代码没有太大区别
template <typename T>
class Foo {
template<typename>
struct FooImpl;
public:
void foo() { FooImpl<T>()(); }
};
template <int N>
class Bar {};
template <typename T>
template <int N>
struct Foo<T>::FooImpl< Bar<N> > {
void operator()() { /* ... */ }
};
int main() {
Foo< Bar<0> > fb;
fb.foo();
Foo<int> fi;
//fi.foo();
}
最后一行没有按预期编译(至少我得到了预期的结果,否则只需为FooImpl
定义函数调用运算符)。
通过这种方式,您可以有选择地定义您希望foo
工作的专业化。在所有其他情况下,尝试使用foo
将导致编译错误。
我想知道C++11中是否存在不同的解决方案。
这是标记调度的一个经典用例,max66已经提出了这个用例。C++98和C++11中的方法甚至语法基本相同。
我相信,这里有一个比max66更干净的实现(运行在godbolt上):
template <class T>
class Foo {
template <class>
struct tag{};
template<class U>
void foo_helper(tag<U>){std::cout << "defaultn";}
void foo_helper(tag<Bar<3> >){std::cout << "specialization for Bar<3>n";}
public:
void foo(){return foo_helper(tag<T>());}
};
原理是一样的;不接受自变量的客户端函数调用基于CCD_ 26自变量构造空类型的辅助函数。然后正常的过载处理剩下的。
只是在这里我使用了一个模板化的catch-all方法。
在C++11中,语法只会略有变化;我们可以说tag<Bar<3>>
而不是tag<Bar<3> >
,因为新的解析规则允许嵌套模板使用V字形。
我们还可以将标签和模板化的foo_helper
捕获到可变模板中,使其更通用:
template <class T>
class Foo {
template <class...>
struct tag{};
template<class... U>
void foo_helper(tag<U...>){std::cout << "defaultn";}
void foo_helper(tag<Bar<3>>){std::cout << "specialization for Bar<3>n";}
public:
void foo(){return foo_helper(tag<T>{});}
};
随着constexpr的引入,C++17中的事情实际上开始变得非常有趣,如果这允许我们编写基于T
(实时演示)的正常分支逻辑:
template <class T>
class Foo {
public:
void foo(){
if constexpr (std::is_same_v<T, Bar<3>>){std::cout << "Specialization for Bar<3>n";}
else std::cout << "defaultn";
}
};
正如您所看到的,所有的标签内容都被抛弃了,取而代之的是使用一个简单的if语句。
我们利用C++11中引入的type_traits来检查T
的类型与我们想要的类型。像这样的东西以前不一定能工作,因为所有分支都需要编译。在C++17中,只编译(在编译时)选择的分支。
请注意,早在C++98中就可以通过使用typeid
(godbolt演示)来模拟这种行为:
void foo(){
if (typeid(T) == typeid(Bar<3>)){std::cout << "Specialization for Bar<3>n";}
else std::cout << "defaultn";
}
然而,typeid
方法是一个糟糕的选择,原因有二:
- 这是一个运行时检查(慢速),用于检查我们在编译时知道的信息
- 它很脆弱,因为所有分支都必须为所有模板实例化进行编译,而在C++17中,
if constexpr
只编译所选的分支
- 如何使用默认参数等选择模板专业化
- 我可以用clang AST从模板专业化中获得默认的模板参数吗
- 带有默认参数的模板专业化
- 还有另一个类模板专业化(在布尔参数上)
- 常量函数参数的专业化
- 嵌套模板类的模板类模板模板参数的专业化
- 遍历模板参数包,没有争论的专业化的作用是什么?
- 继承专业化的模板参数推导
- 如何部分专业化功能以用元组元素作为参数调用功能
- 具有变异模板模板参数的方法的部分专业化
- 功能模板参数包,然后是模板参数和专业化
- 在允许的函数模板的明确专业化中扣除了多个模板参数
- C 中的模板专业化中的字符串参数
- 模板参数的模板专业化
- 对于类型是类模板专业化的参数,ADL背后的理由是什么
- 为什么我们不能在模板专业化的开始/中间使用可变参数模板(以及如何模拟)?
- 操作员与整数模板参数专业化过载
- 错误:类模板部分专业化包含无法推导的模板参数
- C - 具有非类型模板参数的模板类上的专业化函数模板
- 带有模板参数的模板专业化