如果在两种情况下使用expr
而不使用括号,那么decltype(auto)
和decltype(returning expression)
作为函数(模板)的返回类型有什么区别?
auto f() -> decltype(auto) { return expr; } // 1
auto f() -> decltype(expr) { return expr; } // 2
上面的f
可以在任何上下文中定义/声明,可以是(成员)函数或(成员)函数模板,甚至是(泛型)lambda。expr
可以依赖于任何模板参数。
在第二个版本中,两个expr
是完全相同的表达式,没有额外的括号。
在c++ 14和以后的版本中使用第一种或第二种形式,我们可以期望有哪些差异?
如果到处使用括号会怎样?
是有区别的。第一个将根据函数体中的返回表达式检测返回类型。
第二个也不会将返回类型设置为decltype()
内部表达式的类型,但也将在其上应用表达式的。这意味着如果decltype中的表达式无效,编译器将搜索另一个有效的重载。而第一个版本将是一个硬错误。
举个例子:
template<typename T>
auto fun(T a) -> decltype(a.f()) { return a.f(); }
template<typename T>
auto fun(T a) -> decltype(a.g()) { return a.g(); }
struct SomeType {
int g() { return 0; }
};
fun(SomeType{});
选择正确的过载。现在,如果我们将decltype(expr)
替换为decltype(auto)
,编译器将无法选择正确的重载,因为在函数签名中没有约束该类型应该能够做什么。
decltype(auto)
用于
-
泛型代码的前向返回类型,你不想键入很多重复的东西
template<class Func, class... Args> decltype(auto) foo(Func f, Args&&... args) { return f(std::forward<Args>(args)...); }
-
延迟类型扣除,正如你在这个问题中看到的,编译器有一些问题,没有
decltype(auto)
-
这不能很好地工作,正如你在g++和clang++中看到的
template<int i> struct Int {}; constexpr auto iter(Int<0>) -> Int<0>; template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{})); int main(){ decltype(iter(Int<10>{})) a; }
-
效果很好,如下图所示:
template<int i> struct Int {}; constexpr auto iter(Int<0>) -> Int<0>; template<int i> constexpr auto iter(Int<i>) -> decltype(auto) { return iter(Int<i-1>{}); } int main(){ decltype(iter(Int<10>{})) a; }
-
decltype(expr)
:
- 应用SFINAE,而
decltype(auto)
不是
又是一个例子。在decltype(auto)
的情况下,允许使用lambda。在decltype(expr)
的情况下,不允许使用lambda。
这是因为a) lambda不允许作为未求值的操作数(例如在sizeof
或decltype
中);b) decltype
和两个表达式体中的类型将不同,因为它们是两个不同的lambda表达式
// ill-formed: lambda within decltype
auto f() -> decltype([]{}) { return []{}; };
// allowed: lambda is not in decltype, and only appears once
auto f() -> decltype(auto) { return []{}; }