decltype(auto)和decltype(return expr)作为返回类型有什么区别?



如果在两种情况下使用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不允许作为未求值的操作数(例如在sizeofdecltype中);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 []{}; }

最新更新