我们假设有一个template
班Foo
:
template <typename T>
class Foo {
void foo();
};
我有另一个template
班Bar
(独立于第一个):
template <int N>
class Bar {};
让我们说,我想专门针对任何foo()
类的Bar
方法。
我错写了:
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),它允许我专门化并实现一个单独的类方法。 / p>
The question on SO没有解释如何专门使用模板参数。实际上,我的问题显示了编译器如何抱怨这一点。
答案 0 :(得分:3)
对于C ++ 11,您可以在非专业std::enable_if
类中使用SFINAE启用/禁用(使用foo()
)两个不同版本的Foo
。
在C ++ 98中你没有抱歉:我的想法没有因为这种方法需要对C ++ 11创新的方法使用默认模板参数。std::enable_if
但你可以模拟它(给我一些时间,我试着提出一个例子)。
另一种方法是为Foo()
定义模板基类,比如说FooBase
,在foo()
中插入foo()
(并且只有FooBase
)并专门化{{ 1}}。
另一种方式,也适用于C ++ 98,可以是标签分派:你可以定义一个唯一的FooBase
,参数为零,调用另一个foo()
,参数确定foo()
。
以下是完整的(C ++ 98可编译)示例
T
答案 1 :(得分:0)
如果不需要公共基础,另一种方法可能是给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()特化。
答案 2 :(得分:0)
如果您不能专门化foo
,请对其进行定义,以便将调用委托给内部 foo-implementation 类。然后把这个班专门化。
像这样的东西应该在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
将导致编译错误。
答案 3 :(得分:0)
我想知道 C ++ 11 中是否存在不同的解决方案。
这是标记式调度的经典用例,max66已经建议过。这种方法甚至语法在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> >
,因为新的解析规则允许嵌套模板使用V形。
我们还可以将标记和模板化的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>{});}
};
在 C ++ 17 中实际开始变得非常有趣,引入了constexpr if,允许我们根据T
编写看似普通分支逻辑的内容({ {3}}):
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中引入的Live Demo来检查T
的类型与我们想要的类型。这样的事情以前不一定有效,因为需要编译所有分支。在C ++ 17中,只编译了选中的分支(在编译时)。
请注意,您可以使用type_traits(typeid
)在C ++ 98中尽早模拟此行为:
void foo(){
if (typeid(T) == typeid(Bar<3>)){std::cout << "Specialization for Bar<3>\n";}
else std::cout << "default\n";
}
然而,typeid
方法选择不当有两个原因:
if constexpr
中只编译所选的分支。