带有模板参数的模板专业化
Template Specialisation with Template Argument
假设有一个 template
class Foo
:
template <typename T>
class Foo {
void foo();
};
我还有一个template
classBar
(独立于第一个):
template <int N>
class Bar {};
比方说,我想针对任何 Bar
class 专门化 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 中是否存在不同的解决方案.
备注
我可以解决将整个 class Foo
专门化为泛型 Bar
的问题,但之后我必须定义所有方法。
这不是我想要的,我正在寻找(如果存在)更优雅的解决方案(C++98 和 C++11),它允许我专门化并仅实现一个 class 方法.
编辑:
没有解释如何专门化模板参数。事实上,我的问题显示了编译器是如何抱怨的。
对于 C++11,您可以 SFINAE enable/disable(使用 std::enable_if
)两个不同版本的 foo()
在一个未专门化的 Foo
class 中。
在 C++98 中你没有 std::enable_if
但你可以模拟它(给我一些时间,我尝试提出一个例子)。 抱歉: 我的想法行不通,因为此方法需要为 C++11 创新的方法使用默认模板参数。
另一种方法是为Foo()
定义一个模板基础class,比如FooBase
,在[=16中插入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-实现 class。然后专门化 class.
像这样的东西应该在 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
将导致编译错误。
I'd like to know if there exist different solutions in C++11.
这是标记调度的经典用例,其中 已被推荐。 C++98 和 C++11 中的方法,甚至语法基本相同。
我认为这里有一些比 max66 更简洁的实现 (running on godbolt):
template <class T>
class Foo {
template <class>
struct tag{};
template<class U>
void foo_helper(tag<U>){std::cout << "default\n";}
void foo_helper(tag<Bar<3> >){std::cout << "specialization for Bar<3>\n";}
public:
void foo(){return foo_helper(tag<T>());}
};
原理是一样的;不接受任何参数的客户端函数调用一个辅助函数,该函数根据 T
参数构造一个空类型。然后正常的重载会处理剩下的事情。
只有在这里我使用了模板化的包罗万象的方法。
在 C++11 中,语法只会有轻微的变化;我们可以说 tag<Bar<3>>
而不是 tag<Bar<3> >
因为新的解析规则允许嵌套模板使用人字形。
我们还可以将标签和模板化的 foo_helper
包罗万象 variadic templates 变得更加通用:
template <class T>
class Foo {
template <class...>
struct tag{};
template<class... U>
void foo_helper(tag<U...>){std::cout << "default\n";}
void foo_helper(tag<Bar<3>>){std::cout << "specialization for Bar<3>\n";}
public:
void foo(){return foo_helper(tag<T>{});}
};
随着 constexpr if that allows us to write what looks like normal branching logic based on T
(Live Demo 的引入,C++17 中的事情实际上开始变得非常有趣:
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 << "default\n";
}
};
如您所见,所有标签内容都消失了,取而代之的是使用简单的 if 语句。
我们利用 C++11 中引入的 type_traits 来根据我们想要的类型检查 T
的类型。像这样的东西以前不一定有效,因为所有分支都需要编译。在 C++17 中,仅编译选定的分支(在编译时)。
请注意,早在 C++98 中,您就可以通过使用 typeid
(godbolt demo):
来模拟此行为
void foo(){
if (typeid(T) == typeid(Bar<3>)){std::cout << "Specialization for Bar<3>\n";}
else std::cout << "default\n";
}
但是,typeid
方法是一个糟糕的选择,原因有两个:
- 这是一个运行时间检查(慢)我们在编译时知道的信息
- 它很脆弱,因为所有分支都必须针对所有模板实例进行编译,而在 C++17 中
if constexpr
仅编译选定的分支。
假设有一个 template
class Foo
:
template <typename T>
class Foo {
void foo();
};
我还有一个template
classBar
(独立于第一个):
template <int N>
class Bar {};
比方说,我想针对任何 Bar
class 专门化 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 中是否存在不同的解决方案.
备注
我可以解决将整个 class Foo
专门化为泛型 Bar
的问题,但之后我必须定义所有方法。
这不是我想要的,我正在寻找(如果存在)更优雅的解决方案(C++98 和 C++11),它允许我专门化并仅实现一个 class 方法.
编辑:
对于 C++11,您可以 SFINAE enable/disable(使用 std::enable_if
)两个不同版本的 foo()
在一个未专门化的 Foo
class 中。
在 C++98 中你没有 抱歉: 我的想法行不通,因为此方法需要为 C++11 创新的方法使用默认模板参数。std::enable_if
但你可以模拟它(给我一些时间,我尝试提出一个例子)。
另一种方法是为Foo()
定义一个模板基础class,比如FooBase
,在[=16中插入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-实现 class。然后专门化 class.
像这样的东西应该在 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
将导致编译错误。
I'd like to know if there exist different solutions in C++11.
这是标记调度的经典用例,其中
我认为这里有一些比 max66 更简洁的实现 (running on godbolt):
template <class T>
class Foo {
template <class>
struct tag{};
template<class U>
void foo_helper(tag<U>){std::cout << "default\n";}
void foo_helper(tag<Bar<3> >){std::cout << "specialization for Bar<3>\n";}
public:
void foo(){return foo_helper(tag<T>());}
};
原理是一样的;不接受任何参数的客户端函数调用一个辅助函数,该函数根据 T
参数构造一个空类型。然后正常的重载会处理剩下的事情。
只有在这里我使用了模板化的包罗万象的方法。
在 C++11 中,语法只会有轻微的变化;我们可以说 tag<Bar<3>>
而不是 tag<Bar<3> >
因为新的解析规则允许嵌套模板使用人字形。
我们还可以将标签和模板化的 foo_helper
包罗万象 variadic templates 变得更加通用:
template <class T>
class Foo {
template <class...>
struct tag{};
template<class... U>
void foo_helper(tag<U...>){std::cout << "default\n";}
void foo_helper(tag<Bar<3>>){std::cout << "specialization for Bar<3>\n";}
public:
void foo(){return foo_helper(tag<T>{});}
};
随着 constexpr if that allows us to write what looks like normal branching logic based on T
(Live Demo 的引入,C++17 中的事情实际上开始变得非常有趣:
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 << "default\n";
}
};
如您所见,所有标签内容都消失了,取而代之的是使用简单的 if 语句。
我们利用 C++11 中引入的 type_traits 来根据我们想要的类型检查 T
的类型。像这样的东西以前不一定有效,因为所有分支都需要编译。在 C++17 中,仅编译选定的分支(在编译时)。
请注意,早在 C++98 中,您就可以通过使用 typeid
(godbolt demo):
void foo(){
if (typeid(T) == typeid(Bar<3>)){std::cout << "Specialization for Bar<3>\n";}
else std::cout << "default\n";
}
但是,typeid
方法是一个糟糕的选择,原因有两个:
- 这是一个运行时间检查(慢)我们在编译时知道的信息
- 它很脆弱,因为所有分支都必须针对所有模板实例进行编译,而在 C++17 中
if constexpr
仅编译选定的分支。