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

5
考虑以下函数:
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 的行为仍然有些神秘。

2个回答

7

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

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

这将导致SFINAE排除或R将是所选用的myfunction重载的返回类型。
你的问题让我觉得你需要恢复一下引用折叠工作原理的知识,我建议在这个领域进行阅读(似乎是一个不错的起点)。

我要提到这是一种不好的方法,因为很容易通过传递第二个参数来破坏它。 - skypjack
1
@skypjack:没错,这样就避免了任何SFINAE的发生。 - ildjarn
是的,我说“打破它”的意思就是那个意思。如果不清楚的话,抱歉。;-) - skypjack

4

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(); // never defined

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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接