模板化代码的语法和语义是什么C++
What are the syntax and semantics of C++ templated code?
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-reference 复制了这行代码(只有std::enable_if
部分,我确实需要T
和所有三个size_t
),因为我想只在使用它时使用floating_types函数......它不编译。
有人可以向我解释一下,为什么,它甚至做了什么?当我在它的时候,你之后如何调用这个函数?
SO上的每个教程或问题都会被答案轰炸,这很好,但是对于不了解正在发生的事情的杰克***的人来说,即使这些也没有真正的帮助。(啃啪啪,如果可能轻微激动或攻击性)
编辑:我非常感谢到目前为止的所有答案,我意识到我的措辞可能有点不对劲......我了解模板参数是什么,并且知道运行时和编译时等之间的区别,但我只是无法很好地掌握std::enable_if
背后的语法
编辑2:
template<typename T, size_t M, size_t K, size_t N, typename = std::enable_if_t<std::is_integral<T>::value>>
void fastor2d(){
Fastor::Tensor<T,M,K> A; A.randInt();
}
这实际上是我唯一需要更改的内容。注意随机()部分
template<typename T, size_t M, size_t K, size_t N, typename = std::enable_if_t<std::is_floating_point<T>::value>>
void fastor2d(){
Fastor::Tensor<T,M,K> A; A.random();
}
我会尽量简单地解释这一点,不要过多地讨论语言细节,因为你要求它。
模板参数是编译时参数(它们在应用程序运行时不会更改)。函数参数是运行时的,具有内存地址。
调用此函数将如下所示:
fastor2d<Object, 1, 2, 3>();
在<>括号中,您会看到编译时参数或更准确地说是模板参数,在这种情况下,函数在 () 括号中采用 0 个运行时参数。最后一个编译时参数有一个默认参数,用于检查函数是否应该编译(enable_if类型)。如果你想更清楚地知道启用,你应该搜索术语SFINAE,这是一种模板元编程技术,用于确定函数或类是否应该存在。
下面是一个简短的 SFINAE 示例:
template<typename T, typename = std::enable_if_t<std::is_floating_point<T>::value>>
void function(T arg)
{
}
function(0.3f); //OK
function(0.0); //OK double results in std::is_floating_point<double>::value == true
function("Hello"); //Does not exist (T is not floating point)
第三个函数调用失败的原因是该函数不存在。这是因为当作为其模板参数传入的编译时布尔值为 false 时,启用 if 导致函数不存在。
std::is_floating_point<std::string>::value == false
请注意,很多人都同意SFINAE语法很糟糕,并且在C++ 20中引入概念和约束后,将不再需要许多SFINAE代码。
我将采用自下而上的方法,而不是从代码片段开始的自上而下的方法,来解释有关模板的一些重要细节以及所涉及的工具和技术。
从本质上讲,模板是一种工具,可让您编写适用于一系列可能类型的C++代码,而不是严格适用于固定类型。在静态类型语言中,这首先是一个很好的工具,可以在不牺牲类型安全性的情况下重用代码,但特别是在C++中,模板非常强大,因为它们可以专门化。
每个模板声明都以关键字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
,因此您可以安全地摆脱以下功能相同的代码:
print(i);
print(d);
print(s);
但是假设您希望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
一起使用,都会选择专业化。这非常有用,例如,如果通用模板对一种特定类型没有意义。
std::string s = "hello";
Point2D p {0.5, 2.7};
print(s); // > hello
print(p); // > (0.5,2.7)
但是,如果我们想根据一个简单的条件同时为多种类型专门化模板怎么办?这就是事情变得有点元的地方。首先,让我们尝试以一种允许它们在模板中使用的方式表达条件。这可能有点棘手,因为我们需要编译时答案。
这里的条件是T
是一个浮点数,如果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;
但是我们如何在另一个模板中使用此编译时条件呢?当有许多可能的模板专用化可供选择时,我们如何告诉编译器选择哪个模板?
这是通过利用一个名为SFINAE的C++规则来实现的,该规则在基本英语中说,"当有许多可能的模板专业化可供选择,而当前的模板没有意义*时,只需跳过它并尝试下一个。
尝试将模板- 参数替换为模板化代码时,存在一系列错误,这些错误会导致忽略模板,而不会立即出现编译器错误。该列表有点长且复杂。
模板没有意义的一种可能方式是,如果它尝试使用不存在的类型。
template<typename T>
void foo(typename T::nested_type x); // SFINAE error if T does not contain nested_type
这与std::enable_if
在引擎盖下使用的技巧完全相同。enable_if
是一个接受类型T
和bool
条件的模板类,并且它包含一个嵌套类型type
仅当条件为 true 时才等于T
。这也很容易实现:
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,那么我们可以安全地使用嵌套类型。如果我们传递的条件为 false,则不再考虑模板。
template<typename T>
typename 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>
typename 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 是否为浮点数,否则停止并尝试下一个重载">
最后,拆开你的例子:
// 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
的模板,并具有自己的条件集。
首先,我将以工作形式重写您的函数
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
{ }
关键是我已经将表单T
的第二个模板参数更改为int
std::enable_if_t
。
我也在std::enable_if_t
之前删除了typename
,但这并不重要:typename
隐含在std::enable_if_t
末尾的_t
中,从 C++14 引入。在 C++11 中,正确的形式是
// 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
时,如果false
std::enable_if_t< I == 3, int>
std::enable_if_t
的条件不会被替换,所以foo()
没有启用,但这不是一个错误(如果, 通过重载,显然还有另一个启用的foo()
函数与调用匹配)。
那么代码中的问题在哪里呢?
问题是,当第一个模板参数true
时,std::enable_if_t
被替换为第二个参数。
所以如果你写
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
)),因此进行替换并得到
float = 0
但是,不幸的是,模板值(不是类型)参数不能是浮点类型,因此会出现错误。
如果您使用int
而不是T
,则替换会给您
int = 0
这是正确的。
另一种解决方案可以使用以下表格
typename = std::enable_if_t<std::is_floating_point<T>::value, T>
正如安德烈亚斯·洛洛乔所建议的那样,因为换人给了你
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 ()
{ }
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- C++避免重复声明的语法是什么
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- C++从另一个类访问公共静态向量的正确方法是什么
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- 在C++17中,引用const字符串的语义应该是什么
- 在C++中使用移动语义的正确方法是什么?
- 函数作为变量的语义是什么 C++.
- testb $1, %al 的语义是什么
- 模板化代码的语法和语义是什么C++
- C++11 中已删除成员函数的确切语义是什么?
- 消除默认/删除移动/复制语义中涉及的样板的好方法是什么
- 在共享库中全局声明的非pod对象的语义是什么?
- 语义和顶点布局在D3D11中的意义是什么?
- 移动语义 - 它的全部内容是什么?
- 指向已分配对象的指针没有值语义是什么意思
- 在遵循pimpl设计模式的类中实现move语义的正确方法是什么?
- 我是否正确地使用了move语义?好处是什么?
- "值语义"和"指针语义"是什么意思