template< typename T,size_t M,size_t K,size_t N,typename std :: enable_if_t< std :: is_floating_point< T> :: value,T> = 0>void fastor2d(){// …}我从cpp -…复制了这行代码。
我将采用自下而上的方法来解释有关模板的一些重要细节以及涉及哪些工具和技术,而不是从代码片段开始的自上而下的方法。
从本质上讲,模板是一种工具,可以让您编写适用于的C ++代码 范围 可能的类型,不严格的固定类型。在静态类型的语言中,这首先是一个重用代码而不牺牲类型安全性的好工具,但特别是在C ++中,模板非常强大,因为它们可以是 专门 。
每个模板声明都以关键字开头 template ,以及一份清单 类型 要么 非类型 (即 值 )参数。类型参数使用特殊关键字 typename 要么 class ,并用于让您的代码适用于各种类型。非类型参数只使用现有类型的名称,这些允许您将代码应用于范围 值 在编译时已知。
template
typename
class
一个非常基本的模板化函数可能如下所示:
template<typename T> // declare a template accepting a single type T void print(T t){ // print accepts a T and returns void std::cout << t; // we can't know what this means until the point where T is known }
这使我们可以安全地为一系列可能的类型重用代码,我们可以按如下方式使用它:
int i = 3; double d = 3.14159; std::string s = "Hello, world!"; print<int>(i); print<double>(d); print<std::string>(s);
编译器甚至足够聪明,可以推导出模板参数 T 对于其中的每一个,所以你可以安全地使用以下功能相同的代码:
T
print(i); print(d); print(s);
但假设你想要 print 对一种类型表现不同。例如,假设您有自定义 Point2D 需要特殊处理的类。你可以用一个 模板专业化 :
print
Point2D
template<> // this begins a (full) template specialization void print<Point2D>(Point2D p){ // we are specializing the existing template print with T=Point2D std::cout << '(' << p.x << ',' << p.y << ')'; }
现在,我们随时使用 print 同 T=Point2D ,选择专业化。这非常有用,例如,如果通用模板对某个特定类型没有意义。
T=Point2D
std::string s = "hello"; Point2D p {0.5, 2.7}; print(s); // > hello print(p); // > (0.5,2.7)
但是如果我们想要专门化模板呢? 许多 基于简单的条件,一次性类型?这就是事情变得有点元的地方。首先,让我们尝试以允许在模板中使用它们的方式表达条件。这可能有点棘手,因为我们需要编译时的答案。
这里的条件是 T 是一个浮点数,如果是,则为真 T=float 要么 T=double 否则就是假的。实际上,仅使用模板专业化实现起来相当简单。
T=float
T=double
// the default implementation of is_floating_point<T> has a static member that is always false template<typename T> struct is_floating_point { static constexpr bool value = false; }; // the specialization is_floating_point<float> has a static member that is always true template<> struct is_floating_point<float> { static constexpr bool value = true; }; // the specialization is_floating_point<double> has a static member that is always true template<> struct is_floating_point<double> { static constexpr bool value = true; }
现在,我们可以查询任何类型以查看它是否是浮点数:
is_floating_point<std::string>::value == false; is_floating_point<int>::value == false; is_floating_point<float>::value == true; is_floating_point<double>::value == true;
但是我们如何在另一个模板中使用这个编译时条件呢?当有许多可能的模板特化可供选择时,我们如何告诉编译器选择哪个模板?
这是通过利用名为的C ++规则来实现的 SFINAE 用基本的英语说,“当有很多可能的模板时,当前的模板没有意义*,只需跳过它并尝试下一个模板。”
*在尝试将模板参数替换为模板化代码时,会出现一个错误列表,导致模板被忽略 没有立即编译错误 。列表有点 漫长而复杂 。
模板没有意义的一种可能方式是它是否尝试使用不存在的类型。
template<T> void foo(T::nested_type x); // SFINAE error if T does not contain nested_type
这是完全相同的技巧 std::enable_if 在引擎盖下使用。 enable_if 是一个接受类型的模板类 T 和a bool condition,它包含一个嵌套类型 type 等于 T 只有当条件成立时 。这也很容易实现:
std::enable_if
enable_if
bool
type
template<bool condition, typename T> struct enable_if { // no nested type! }; template<typename T> // partial specialization for condition=true but any T struct enable_if<true, T> { typedef T type; // only exists when condition=true };
现在我们有一个帮助器,我们可以用它来代替任何类型。如果我们传递的条件为true,那么我们可以安全地使用嵌套类型。如果我们传递的条件是假的,那么 不再考虑模板。
template<typename T> std::enable_if<std::is_floating_point<T>::value, void>::type // This is the return type! numberFunction(T t){ std::cout << "T is a floating point"; } template<typename T> std::enable_if<!std::is_floating_point<T>::value, void>::type numberFunction(T t){ std::cout << "T is not a floating point"; }
我完全同意 std::enable_if<std::is_floating_point<T>::value, void>::type 拼写一种类型是一种混乱的方式。你可以把它读成“ void 如果T是浮点数,那么无意义的废话。“
std::enable_if<std::is_floating_point<T>::value, void>::type
void
最后,要拆开你的例子:
// we are declaring a template template< typename T, // that accepts some type T, size_t M, // a size_t M, size_t K, // a size_t K, size_t N, // a size_t N, // and an unnamed non-type that only makes sense when T is a floating point typename std::enable_if_t<std::is_floating_point<T>::value, T> = 0 > void fastor2d(){//...}
请注意 = 0 在末尾。这只是最终模板参数的默认值,它可以让您轻松指定 T , M , K ,和 N 但不是第五个参数。该 enable_if 这里使用意味着您可以提供其他模板 fastor2d ,有自己的条件。
= 0
M
K
N
fastor2d
首先,我将以工作形式重写您的功能
template <typename T, size_t M, size_t K, size_t N, std::enable_if_t<std::is_floating_point<T>::value, int> = 0> void fastor2d() // ..........................................^^^ int, not T { }
关键是我已经改变了第二个模板参数 std::enable_if_t 形成 T 至 int 。
std::enable_if_t
int
我也删除了 typename 之前 std::enable_if_t 但并不重要: typename 隐含在 _t 在......的最后 std::enable_if_t ,从C ++ 14开始。在C ++ 11中,正确的形式是
_t
// C++11 version typename std::enable_if<std::is_floating_point<T>::value, int>::type = 0 // ^^^^^^^^ no _t ^^^^^^
但为什么它有效呢?
从名称开始:SFINAE。
是“替换失败不是错误”的简短形式。
这是一个C ++规则,所以当你写一些东西时
template <int I, std::enable_if_t< I == 3, int> = 0> void foo () { }
和 I 是 3 ,条件 std::enable_if_t 是 true 所以 std::enable_if_t< I == 3, int> 用。代替 int 所以 foo() 已启用,但何时启用 I 不 3 ,条件 std::enable_if_t 如果 false 所以 std::enable_if_t< I == 3, int> 不是这样替代的 foo() 没有启用,但这不是一个错误(如果,通过重载,还有另一个 foo() 功能,启用,匹配呼叫,显然)。
I
3
true
std::enable_if_t< I == 3, int>
foo()
false
那么代码中的问题在哪里?
问题是 std::enable_if_t 当第一个模板参数为时,将替换 true ,第二个参数。
所以,如果你写
std::enable_if_t<std::is_floating_point<T>::value, T> = 0
你打电话
fastor2d<float, 0u, 1u, 2u>();
该 std::is_floating_point<float>::value (但你也可以使用较短的形式 std::is_floating_point_v<T> ( _v 并不是 ::value ))所以替换发生,你得到
std::is_floating_point<float>::value
std::is_floating_point_v<T>
_v
::value
float = 0
但是,遗憾的是,模板值(非类型)参数不能是浮点类型,因此会出错。
如果你使用 int 代替 T ,替代给你
int = 0
这是正确的。
另一种解决方案可以使用以下形式
typename = std::enable_if_t<std::is_floating_point<T>::value, T>
正如Andreas Loanjoe所建议的那样,因为替换会给你
typename = float
这是一个有效的语法。
但是,当您想要编写两个备用函数时,此解决方案的缺点是不起作用,如下例所示
// the following solution doesn't works template <typename T, typename = std::enable_if_t<true == std::is_floating_point<T>::value, int>> void foo () { } template <typename T, typename = std::enable_if_t<false == std::is_floating_point<T>::value, int>> void foo () { }
哪里的解决方案基于价值
// the following works template <typename T, std::enable_if_t<true == std::is_floating_point<T>::value, int> = 0> void foo () { } template <typename T, std::enable_if_t<false == std::is_floating_point<T>::value, int> = 0> void foo () { }