尝试禁用无参数成员函数时,SFINAE 在 decltype() 内部不工作

SFINAE not working inside decltype() when attempting to disable a no-args member function

我已将问题提炼为以下 class,它试图使用 std::enable_if 禁用成员函数:

#include <type_traits>

int foo(int& x) {
    return x;
}

template<bool enable>
struct A {
int x;
template<bool dummy=true,
        typename Enabler = typename std::enable_if<dummy && enable>::type>
 auto disabled_method() const -> decltype(foo(x)) {

    return foo(x);
 }
};

int main() {
    A<false> x;
}

表达式 decltype(foo(x)) 中存在类型错误,尽管 enable_if!

应该禁用该函数

请注意,它特别出现在 return 类型的函数中。例如,如果我们将 decltype(foo(x)) 移动到函数体,SFINAE 将正常工作:

template<bool dummy=true,
        typename Enabler = typename std::enable_if<dummy && enable>::type>
 auto disabled_method() const -> int {
    decltype(foo((X)x)) local;
    return foo(x);
 }

(与使用 decltype(foo((X)x)) 作为参数类型相同)

同样,如果我将其声明为

,它将正确执行 SFINAE
template<bool dummy=true,
        typename X = const int,
        typename Enabler = typename std::enable_if<dummy && enable>::type>
 auto disabled_method() const -> decltype(foo((X)x)) {

    return foo(x);
 }

但是使用decltype(foo((const int)x)),它会出错,尽管上面的X等于const int。这似乎表明将额外的转换引入模板参数 X 会导致它延迟替换。

但是上面的 dummyEnabler 模板模式无论如何都应该这样做吗?

为什么会这样?

return 类型是 SFINAE 过程的一部分,这就是为什么您还可以将 std::enable_if 作为 return 类型的一部分的原因。但是 SFINAE 仅在直接上下文中存在错误时才起作用,但是 foo(x) 不是直接模板上下文的一部分,因此编译器会抛出错误。

例如,如果将代码更改为以下内容

template<bool dummy=true>
std::enable_if_t<dummy && enable, decltype(foo(x))> disabled_method()
        const {
    return foo(x);
}

然后 enable_if 工作,因为 decltype(foo(x)) 不是 enable_if 本身的一部分,并且在 x 是非常量的直接上下文之外

查看此问题及其答案,了解有关直接上下文是什么的更多信息What exactly is the "immediate context" mentioned in the C++11 Standard for which SFINAE applies?


为方便起见,引用链接问题的答案来解释直接上下文是什么

If you consider all the templates and implicitly-defined functions that are needed to determine the result of the template argument substitution, and imagine they are generated first, before substitution starts, then any errors occurring in that first step are not in the immediate context, and result in hard errors.

在您的示例中,需要 return 类型来确定模板参数替换的结果,并且独立于替换过程,因此会导致硬错误。


同样在这种情况下,我通常发现更容易完全忽略 return 类型,让编译器推断它。它更容易使用。

这与"immediate context"无关。

通常,非依赖结构中的错误会导致程序格式错误的 NDR(无需诊断),请参阅 [temp.res]/8。实现有很大的回旋余地来诊断或不诊断此类问题。

在您的例子中,foo(x) 不依赖于模板参数,并且总是格式错误;因此,即使 A 从未实例化,实现也可以自由诊断它。