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

What is difference between decltype(auto) and decltype(returning expr) as return type?

如果 decltype(auto)decltype(returning expression) 作为 return 类型的函数(模板)有什么区别,如果 expr 在两种情况下都没有括号使用?

auto f() -> decltype(auto) { return expr; } // 1
auto f() -> decltype(expr) { return expr; } // 2

以上 f 在任何上下文中都可以是 defined/declared,并且可以是(成员)函数或(成员)函数模板,甚至是(通用)lambda。 expr 可以依赖于任何模板参数。

在第二个版本中,两个 expr 是完全相同的表达式,没有额外的括号。

在 C++14 及更高版本中使用第一种或第二种形式时,可以预料到哪些差异?

如果到处都使用括号会怎样?

是的,有区别。第一个将根据函数体中的 return 表达式检测 return 类型。

第二个不会把return类型也设置成decltype()里面表达式的类型,还会在上面套用expression sfinae .这意味着如果 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{});

这是 select 正确的重载。现在,如果我们用 decltype(auto) 替换 decltype(expr),编译器将无法 select 正确的重载,因为函数签名 中没有 限制类型应该能够做什么。

decltype(auto)用于

  • 转发return输入通用代码,你不想输入很多重复的东西

    template<class Func, class... Args>
    decltype(auto) foo(Func f, Args&&... args) 
    { 
        return f(std::forward<Args>(args)...); 
    }
    
  • 延迟类型推导,如您在 this question 中所见,编译器在输出 decltype(auto)

    时存在一些问题
    • 正如您在 g++ and 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;
      }
      
    • 如您所见here:

      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 []{}; }