我不完全确定为什么@Ryan Calhoun specialized the way he did 但这里有一个更简洁的例子:
// class we want to specialize with later on
template<typename T, typename S>
struct SomeRandomClass
{
int myInt = 0;
};
// non-specialized class
template<typename T>
struct MyTemplateClass
{
void DoSomething(T & t)
{
std::cout << "Not specialized" << std::endl;
}
};
// specialized class
template<typename T, typename S>
struct MyTemplateClass< SomeRandomClass<T, S> >
{
void DoSomething(SomeRandomClass<T,S> & t)
{
std::cout << "Specialized" << std::endl;
}
};
您可以看到您不需要接受的答案中使用的冗余语法:
template<>
template<typename T, typename S>
Working Demo
另类
您可以在非专业化类中使用 type_traits 和 tag-dispatch 来专门化函数。
让我们先为is_random_class做一个概念:
// concept to test for whether some type is SomeRandomClass<T,S>
template<typename T>
struct is_random_class : std::false_type{};
template<typename T, typename S>
struct is_random_class<SomeRandomClass<T,S>> : std::true_type{};
然后让我们再次声明我们的MyTemplateClass,但这次不是模板化(因为我们没有专门化)所以我们称之为MyNonTemplatedClass:
class MyNonTemplatedClass
{
public:
template<typename T>
void DoSomething(T & t)
{
DoSomethingHelper(t, typename is_random_class<T>::type());
}
// ...
注意DoSomething 现在是如何模板化的,它实际上是在调用帮助程序而不是实现逻辑本身?
让我们分解一下:
DoSomethingHelper(t, typename is_random_class<T>::type());
-
t 和以前一样;我们传递T& 类型的参数
-
typename is_random_class<T>::type()
-
is_random_class<T> 是我们的概念,因为它派生自 std::true_type 或 std::false_type,它将在类中定义一个 ::type(谷歌为“类型特征”)
-
::type()“实例化”is_random_class<T>::type 指定的类型。我之所以用引号表示,是因为我们真的会像稍后看到的那样把它扔掉
-
typename 是必需的,因为编译器不知道 is_random_clas<T>::type 实际上命名了一个类型。
现在我们准备看看MyNonTemplatedClass 的其余部分:
private:
//use tag dispatch. If the compiler is smart it won't actually try to instantiate the second param
template<typename T>
void DoSomethingHelper(T&t, std::true_type)
{
std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
}
template<typename T>
void DoSomethingHelper(T&t, std::false_type)
{
std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
}
};
Full Working Demo v2 Here
请注意,我们的辅助函数名称相同,但重载了第二个参数的类型。我们没有给参数命名,因为我们不需要它,希望编译器会优化它,同时仍然调用正确的函数。
我们的概念仅在T 是SomeRandomClass 类型时才强制DoSomethingHelper(T&t, std::true_type),并为任何其他类型调用另一个。
标签调度的好处
标签分派的主要好处是,如果您只想在该类中专门化单个函数,则无需专门化整个类。
标签调度将在编译时发生,如果您尝试仅在 DoSomething 函数内对概念执行分支,您将无法获得。
C++17
在 C++17 中,使用变量模板 (C++14) 和 if constexpr (C++17),这个问题变得非常容易。
我们使用我们的 type_trait 创建一个变量模板,如果提供的类型 T 是 SomeRandomClass 类型,它将给我们一个 bool 值 true,否则为 false:
template<class T>
constexpr bool is_random_class_v = is_random_class<T>::value;
然后,我们在 if constexpr 表达式中使用它,该表达式仅编译适当的分支(并在编译时丢弃另一个,因此检查是在 编译时,而不是运行时):
struct MyNonTemplatedClass
{
template<class T>
void DoSomething(T& t)
{
if constexpr(is_random_class_v<T>)
std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
else
std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
}
};
type-traits 是一种无需类特化即可模拟这种情况的方法。
请注意,这里的is_random_class 是任意约束的替代。一般来说,如果您只检查单个非模板类型,则更喜欢普通重载,因为它在编译器上效率更高。
Demo
C++20
在C++20中,我们可以更进一步,通过在模板化成员函数上使用requires 子句来使用constraint 而不是if constexpr。缺点是我们再次回到两个函数;一个匹配约束,另一个不匹配:
struct MyNonTemplatedClass
{
template<class T> requires is_random_class_v<T>
void DoSomething(T& t)
{
std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
}
template<class T> requires !is_random_class_v<T>
void DoSomething(T&)
{
std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
}
};
Demo
同样在 C++ 20 中,我们可以显式编码一个概念并使用缩写模板语法:
template<class T>
concept IsRandomClass = is_random_class_v<T>;
template<class T>
concept IsNotRandomClass = !is_random_class_v<T>;
// ...
template<IsRandomClass T>
void DoSomething(T& t)
{ /*...*/}
template<IsNotRandomClass T>
void DoSomething(T&)
{ /*...*/}
Demo