这个答案有一个这样的代码片段:
template<class T, class F>
auto f(std::vector<T> v, F fun)
-> decltype( bool( fun(v[0] ) ), void() )
{
// ...
}
它确实可以编译并运行(至少在 Ideone 上)。
那么,这种情况下类型是如何推导的呢?
c++11 标准真的允许下一行吗?
decltype( bool( fun(v[0] ) ), void() )
我快速浏览了一下,看起来不太对劲。在这种情况下,ideone 错了吗?
c++11 标准中的所有示例都是这样的,它们在 decltype 中都只有一种类型:
struct A {
char g();
template<class T> auto f(T t) -> decltype(t + g())
{ return t + g(); }
};
另一个例子:
void f3() {
float x, &r = x;
[=] {
decltype(x) y1;
decltype((x)) y2 = y1;
decltype(r) r1 = y1;
decltype((r)) r2 = y2;
};
还有另一个
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = i;
decltype(i) x2;
decltype(a->x) x3;
decltype((a->x)) x4 = x3;
它们在 decltype 中都只有一个参数。为什么上面的代码有两个参数(用逗号分隔)?
我创建了另一个示例(无法编译):
#include <vector>
#include <iostream>
template<class T, class F>
auto f(std::vector<T> v, F fun) -> decltype(bool(fun(v[0])), void())
{
// ...
(void)v;(void)fun;
return fun(v.size());
}
void ops(int)
{
}
int main(){
std::vector<int> v;
f(v, [](int){ return true; });
f(v,ops);
}
即使删除了
f(v,ops);
行,f
模板函数的返回类型也会被评估为 void。
decltype( bool( fun(v[0] ) ), void() )
使用 逗号运算符。
分解它,
bool( fun(v[0] ) ), void()
由两个表达式组成;第一个
bool( fun(v[0] ) )
被评估1并被丢弃,为整体表达式提供值
void()
这是类型为 void
的值
2。
decltype
然后生成表达式的类型,如上所示为 void
。
这里使用逗号运算符的原因是为了确保只有第一个子表达式有效时整个表达式才有效;这是因为如果第一个子表达式无效,则在 SFINAE 中使用它来将其排除在替换考虑之外。
这是有效的,因为虽然
decltype
在语法上看起来像一个函数,但它实际上是一种语言构造,(如 sizeof
)被定义为采用单个参数。将逗号运算符参数括起来可能会更清楚:
decltype( ( bool( fun(v[0] ) ), void() ) )
bool( fun(v[0] ) )
并未实际计算,因为我们处于非计算上下文(decltype
,类似于sizeof
)。这里重要的是,如果对整个表达式进行求值,则它会被求值,因此,如果子表达式无效,则整个表达式也无效。
void()
really一个值,但它的行为类似于逗号运算符和
decltype
上下文中的值。
decltype
产生括号之间的表达式的类型,
无需实际评估它(在接下来的部分中请记住这一点)。
,
运算符计算左侧参数/表达式,丢弃结果,计算右侧参数,并产生该结果。因此,返回类型变为
void
。对于
bool(fun(v[0]))
部分来说,相当简单。
bool(f(...))
根据调用 f
的结果构造一个 bool 临时值。如果 f
的返回类型不能转换为 bool
,这将触发错误,由于位于 decltype
内部,这将导致 SFINAE(这称为 “表达式 SFINAE”)。
f(v[0])
会将
v[0]
的返回值传递给f
,其类型为T&
。如果 f
没有 T&
可转换的参数,或者需要更多/更少的参数,这将触发错误,并再次导致 SFINAE,原因与上述相同。(如果
std::vector
不支持
operator[]
,也会发生同样的情况。)