为什么此 lambda 表达式中需要尾随 return 类型?

Why is the trailing return type necessary in this lambda expression?

考虑以下代码:

#include <iostream>
#include <type_traits>
#include <functional>
#include <utility>

template <class F>
constexpr decltype(auto) curry(F&& f)
{
    if constexpr (std::is_invocable_v<decltype(f)>)
    {
        return std::invoke(f);
    }
    else
    {
        return
        [f = std::forward<std::decay_t<F>>(f)]<typename Arg>(Arg&& arg) mutable -> decltype(auto)
        {
            return curry(
            [f = std::forward<std::decay_t<F>>(f), arg = std::forward<Arg>(arg)](auto&& ...args) mutable
            -> std::invoke_result_t<decltype(f), decltype(arg), decltype(args)...>     // #1
            {
                return std::invoke(f, arg, args...);
            });
        };
    }
}

constexpr int add(int a, int b, int c)
{
    return a + b + c;
}

constexpr int nullary()
{
    return 1;
}

void mod(int& a, int& b, int& c)
{
    a = 1;
    b = 2;
    c = 3;
}

int main()
{
    constexpr int u = curry(add)(1)(2)(3);
    constexpr int v = curry(nullary);
    std::cout << u << '\n' << v << std::endl;
    int i{}, j{}, k{};
    curry(mod)(std::ref(i))(std::ref(j))(std::ref(k));
    std::cout << i << ' ' << j << ' ' << k << std::endl;
}

尾随 return 类型,代码可以编译并输出:(godbolt)

6
1
1 2 3

如果去掉看似多余的尾随return类型(上面代码中第#1行),让编译器推导出return类型,然而,编译器开始编译那 error C2672: 'invoke': no matching overloaded function found (godbolt),这让我很惊讶。

那么,为什么在此 lambda 表达式中需要尾随 return 类型?

如果您需要为 lambda 显式指定 return 类型,它必须是尾随 return 类型,因为这是语法允许的唯一方式。

[](int* p) -> int& { return *p; }  // OK 
int& [](int* p) { return *p; }     // ill-formed 

如果函数模板的 return 类型取决于涉及模板参数类型值的表达式的结果,使用尾随 return 类型可能会使您的代码明显不那么冗长。

// with trailing return type 
template <class T, class U> 
auto sum(T t, U u) -> decltype(t + u); 
// with leading return type 
template <class T, class U> 
decltype(std::declval<T>() + std::declval<U>()) sum(T t, U u); 

如果class成员函数的return类型是class的成员类型,使用尾部return类型可以避免冗余限定的需要.

class AReallyLongClassName { 
  // ... 
  class iterator { /* ... */ }; 
  iterator begin(); 
  // ... 
}; 
// ill-formed; `iterator` has not been declared 
iterator AReallyLongClassName::begin() { 
  // ... 
} 
// OK but verbose 
AReallyLongClassName::iterator AReallyLongClassName::begin() { 
  // ... 
} 
// OK, and much less verbose 
auto AReallyLongClassName::begin() -> iterator { 
  // ... 
} 

事实上,在易用性方面,尾随 return 类型语法似乎优于前导 return 类型语法。我也喜欢这样一个事实,它使 C++ 函数声明类似于数学中的函数声明(f:V→R 等)。

我个人的建议是尽可能多地使用尾随 return 类型。不幸的是,Google C++ 风格指南大多不允许使用它们,理由是“有些读者可能会觉得它不熟悉”和“风格统一”。

如果您明确给出 return 类型并且 std::is_invocable_v<decltype(f), decltype(arg), decltype(args)...>false SFINAE 应用并且测试 lambda 是否可调用将简单地导致 false .

然而,如果没有明确的 return 类型,则需要推导 return 类型(在这种情况下是因为 std::is_invocable_v<decltype(f)> 被应用于 lambda)并且为了推导return 键入需要实例化的 lambda 主体。

然而,这也实例化了表达式

return std::invoke(f, arg, args...);

如果 std::is_invocable_v<decltype(f), decltype(arg), decltype(args)...>false,则 ill-formed。由于此替换错误出现在定义中而不是声明中,因此它不在直接上下文中,因此是一个硬错误。