std::forward()
与此问题无关,constexpr
也是如此。所以让我们从超级基础开始,一个只返回其参数值的函数:
template <class T>
auto f(T x) -> decltype(x);
当然,我们可以直接使用T
,但那太容易了。现在的问题是,我们如何将其提升为模板参数,同时仍保持SFINAE(虽然显然在这里没有可能的替换失败,但请跟着我来):
template <class T, class R = decltype(x)>
R f(T x);
因此,这不起作用,因为尚未有
x
(或更糟的是,在名称查找中找到了一些不相关的
x
)。 我们可以在尾随返回类型中使用参数名称,因为它们在作用域内,但我们不能将它们用作表达式SFINAE的默认模板参数的一部分,因为它们尚未在作用域中。
但是,由于这些是模板参数,我们不关心它们的值。 我们只关心它们的类型。 这不是一个计算表达式。 因此,我不需要
x
,我需要具有与
x
相同类型的东西。 第一步可能是:
template <class T, class R = decltype(T{})>
R f(T x);
只要T
是默认可构造的,这个方法可以运行。但我正在写一个模板,我不想对我不需要做出假设的类型进行假设。因此,我可以这样做:
template <class T> T make();
template <class T, class R = decltype(make<T>())>
R f(T x);
现在我们有了类型为T
的任意表达式,我们可以在默认模板参数中使用decltype
。虽然,对于make()
我们仍然有一些限制——有些类型无法从函数中按值返回(例如数组),因此添加引用更加有用。并且我们需要一个不太可能发生冲突的名称,而不是make
:
template <class T>
add_rvalue_reference<T> declval();
template <class T, class R = decltype(declval<T>())>
R f(T x);
std::declval<T>
的精髓在于为您提供T
类型的表达式,但处于未求值的上下文中。
回到您最初的问题。我们使用与从decltype(x)
到decltype(declval<T>())
的转换相同的思路,只是将其应用于不同的表达式。我们不再是使用x
,而是使用myfunction(std::forward<T>(x))
。这就是说,我们正在以与我们的参数相同的类型调用myfunction
:
template <class T, class R = decltype(myfunction(std::declval<T&&>()))>
R f(T&& x);
但由于引用折叠规则,std::declval<T&&>
实际上与 std::declval<T>
是相同的函数,因此我们可以直接写:
template <class T, class R = decltype(myfunction(std::declval<T>()))>
R f(T&& x);