什么时候 forward<T> 等同于 forward<decltype(Targ)> ?

When Is forward<T> equivalent to forward<decltype(Targ)> ?

假设我想要一个通用的高阶函数。最常见的方法是:

一个。通用 lambda

auto F1 = [](auto f) { /*...*/ }

乙。函数模板

template<class F> auto F2(F&& f) { /*...*/ }

我的问题是如何在高阶函数中使用 f。虽然在情况 A 中只有一种方法可以做到:

std::forward<decltype(f)>(f)(...); 

情况 B 至少有两种方法:

std::forward<decltype(f)>(f)(...); 
std::forward<F>(f)(...); 

这些方法是否等效?如果不是,"tie breaking" 的一些示例是什么?

正如所写,案例 A 和案例 B 的声明不同。案例 B 可以推导出左值或右值引用,但案例 A 只能推导出值类型。 auto 的推导类似于模板参数,因此将 auto 更改为 auto&& 以使声明匹配。

为了回答您的问题,它们 等效的。唯一的区别是引用折叠发生在情况 B 中。 decltype(f) 将始终是引用,而当传递给 F2 的参数是右值时 F 将是值类型:

F2(<lvalue>); decltype(f) == T&,  F == T&
F2(<rvalue>); decltype(f) == T&&, F == T

这对 std::forward 无关紧要,因为引用折叠总是会产生正确的类型。

template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t );

如果 TF&&,则 return 类型仍然是 T&&,因为 T&& && == T&&。如果 T 只是一个 F(值类型),那么 return 类型仍然相同(T && == T&&)。可以在 this site.

上找到参考折叠规则

TLDR:没有有效差异,因为引用折叠产生相同的类型。

让我们来看看当您使用不同类型的参数调用 F2 时发生的情况 - 右值引用、const 左值引用和非 const 左值引用。我们不讨论 volatile,因为它基本上不添加任何新信息(它的行为就像 constconst volatile),并且为了简单起见,假设您传递不同的 int

首先推导出F的类型:

  • 如果您传递 int&&F 将是 int,但是 (decltype(f) == int&&).
  • 如果您传递 const int&F 将是 const int& (decltype(f) == const int&)
  • 如果您传递 int&F 将是 int& (decltype(f) == int&)

(请参阅 Scott Meyers 的 blog, talk or slides 以获得全面的解释)。

正如您所看到的后两种情况,decltype(f) == F 因此没有太多需要进一步分析的地方。但是,对于第一种情况,它们是不同的,所以让我们更深入地研究一下。

然后将 Fdecltype(f) 作为模板参数传递给 std::forwardstd::forward takes in a std::remove_reference<T>&std::remove_reference<T>&& 因此两种情况下的参数类型相同。但是,return 类型可能会有所不同(因为我们为其传递了不同的 T - intint&&)。然而,由于编译器执行的引用折叠(再次,请参阅 Scott 的详细解释),int&& && 简单地变为 int&&。所以 return 值也是一样的。

总而言之,对于 std::forward 使用 decltype(f)F 没有区别。

此外,这里有一些 live 示例,它显示了所有推导的类型,即使对于像 const volatile int &&.

这样无用(但有效)的野兽也是如此