从模板类型而不是参数获取转发类型

Get forwarded type from the template type and not the argument

考虑以下函数:

template <class T>
constexpr /* something */ f(T&& x) {
    // do something
}

假设我想根据传递给名为 myfunction 的函数的转发参数的类型来执行 sfinae。实现此目的的一种方法是:

template <class T>
constexpr auto f(T&& x) -> decltype(myfunction(std::forward<T>(x))) {
    // do something
}

有没有办法在模板级别执行此操作:

// This code won't compile
template <class T, class R = decltype(myfunction(std::forward<T>(x)))>
constexpr R f(T&& x) {
    // do something
}

只是我还没有访问 x 的权限,所以这段代码无法编译。有没有办法仅基于T(可能使用std::declval)来实现这一点?

注意:这不是 X/Y 问题,它只是一个示例来说明这种情况发生的位置:我不知道如何在不访问变量的情况下通过转发来执行 SFINAE,因为对我来说std::forward还是有点神秘

是的,std::declval确实是关键:

template <class T, class R = decltype(myfunction(std::declval<T>()))>
constexpr R f(T&& x) {
    // do something
}

这将要么 SFINAE 输出,要么 R 将成为 return 类型的 myfunction 选择的重载。

你的问题让我觉得你需要复习一下引用折叠的工作原理;我建议阅读该领域的内容(this 似乎是一个很好的起点)。

std::forward()与本题无关。 constexpr 也没有。因此,让我们从最基本的函数开始,该函数只是 return 按值传递其参数:

template <class T>
auto f(T x) -> decltype(x);

当然,我们可以直接使用 T,但这太简单了。现在的问题是,我们如何在保持 SFINAE 的同时将其提升到模板参数中(假设这里显然没有可能的替换失败,但请耐心等待):

template <class T, class R = decltype(x)>
R f(T x);

所以这不起作用,因为还没有 x(或者更糟的是,名称查找在其他地方找到了一些不相关的 x)。我们可以在 trailing-return-type 中使用参数名称,因为它们在范围内,但我们不能在默认模板参数中将它们用作 expression-SFINAE 的一部分,因为它们尚未在范围内。

但是因为这些是模板参数,所以我们不关心它们的。我们只关心他们的类型。这不是一个计算表达式。所以我不需要 x,我需要和 x 类型相同的东西。第一步可能是:

template <class T, class R = decltype(T{})>
R f(T x);

这可行...只要 T 是默认构造的。但我正在编写一个模板,我不想对我不需要的类型做出假设。因此,我可以做类似的事情:

template <class T> T make(); // never defined

template <class T, class R = decltype(make<T>())>
R f(T x);

现在我们有了 T 类型的任意表达式,我们可以在 decltype 中使用默认模板参数。虽然,我们 仍然 有点受 make() 的限制 - 有些类型你不能 return 按函数的值(例如数组),所以事实证明添加引用更有用。我们需要一个比 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>()) 的相同思维过程,但只是将其应用于不同的表达式。我们有 myfunction(std::forward<T>(x)) 而不是 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);