带有模板参数的模板专业化

Template Specialisation with Template Argument

本文关键字:专业化 参数      更新时间:2023-10-16

让我们假设有一个templateFoo:

template <typename T>
class Foo {
void foo();
};

我有另一个templateBar(独立于第一个):

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中,你没有std::enable_if,但你可以模拟它(给我几分钟时间,我试着提出一个例子)抱歉:我的想法不起作用,因为这个方法需要为C++11的创新方法使用默认的模板参数。

另一种方法是为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方法是一个糟糕的选择,原因有二:

  1. 这是一个运行时检查(慢速),用于检查我们在编译时知道的信息
  2. 它很脆弱,因为所有分支都必须为所有模板实例化进行编译,而在C++17中,if constexpr只编译所选的分支