为什么编译器在删除模板时不选择其他重载?

Why doesn't the compiler choose the other overload when I remove template?

提问人:Erik Nouroyan 提问时间:7/1/2023 更新时间:7/1/2023 访问量:72

问:

我正在尝试编写 C++ 代码来了解某个类是否具有默认构造函数(没有 C++20 概念)。以下代码工作正常。

#include <iostream>

class C {
public:
    C() = delete;
};

template<typename T>
struct HasDefaultConstructor {
    template<typename U>
    static auto test(int) -> decltype(U(), std::true_type{});

    template<typename U>
    static auto test(...) -> std::false_type;

    static constexpr bool value = decltype(test<T>(0))::value;
};

int main() {
    bool a = HasDefaultConstructor<C>::value;
    std::cout << std::boolalpha << a << std::endl;
    return 0;
}

但是,当我将测试函数更改为以下函数并使用时,会出现编译错误。decltype(test(0))::value

    static auto test(int) -> decltype(T(), std::true_type{});
    static auto test(...) -> std::false_type;

错误说这是一个已删除的函数。我想知道为什么编译器在它是模板化版本时不抱怨,为什么当版本编译失败时它不选择()的其他版本?您能解释一下SFINAE在这里是如何工作的吗?C::C()testtest(...)int

C++ 模板 template-元编程 sfinae

评论

1赞 dalfaB 7/1/2023
SFINAE 仅适用于模板。实例化模板时,如果其声明无效,则该模板不存在。在第二次尝试时,这些函数不是模板,因此 SFINAE 不适用,与任何函数一样的函数必须有效。从C++20开始,我们有了需求,并且需求可以通过应用于非模板。

答:

3赞 JeJo 7/1/2023 #1

我想知道为什么编译器在它是模板化版本时不抱怨,为什么当版本编译失败时它不选择()的其他版本?testtest(...)int

为了使用 SFINAE,template 参数必须来自 SFINAE 的函数模板重载。

cppreference.com

此规则适用于函数模板的重载解析:当将显式指定或推导的类型替换为模板参数失败时,将从重载集中丢弃专用化,而不会导致编译错误。

在第二种情况下,它们不再是函数模板,而是使用类模板参数来定义其尾随返回类型的成员。

template<typename T>
struct HasDefaultConstructor
{
   // following are not function templates!!
   static auto test(int) -> decltype(T(), std::true_type{});
   static auto test(...) -> std::false_type;
   // ....
};

即您直接使用类模板中的模板参数类型,而不是函数的模板参数(即 )。这会更改行为,因为即使未计算表达式,也会在替换阶段解析类型。TUTT()

问题在于,失败的替换发生在函数模板的返回类型中,这不是 SFINAE 直接上下文的一部分。因此,编译器会报告错误,而不是应用 SFINAE 并选择第二个重载。