为什么 static_assert 会破坏替换?

Pau*_*ulo 3 c++ static-assert sfinae

请考虑以下 C++14 代码:

#include <type_traits>

template<typename T>
class Bar {
    static_assert(std::is_destructible<T>::value, "T must be destructible");
};

template<typename T>
void foo(Bar<T> const &) {}

template<typename T>
void foo(T const &) {}

class Product {
public:
    static Product *createProduct() { return new Product{}; }
    static void destroyProduct(Product *product) { delete product; }
private:
    Product() = default;
    ~Product() = default;
};

int main()
{
    Product* p = Product::createProduct();
   
    foo(*p);            // call 1: works fine
    foo<Product>(*p);   // call 2: fails to compile
       
    Product::destroyProduct(p);
           
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

以及来自 clang 的错误消息:

error: static_assert failed due to requirement 'std::is_destructible<Product>::value' "T must be destructible"
    static_assert(std::is_destructible<T>::value, "T must be destructible");
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: in instantiation of template class 'Bar<Product>' requested here
    foo<Product>(*p);   // call 2: fails to compile
                 ^
note: while substituting deduced template arguments into function template 'foo' [with T = Product]
    foo<Product>(*p);   // call 2: fails to compile
    ^
1 error generated.
Run Code Online (Sandbox Code Playgroud)

我的理解是 和call 1call 2应该编译良好,但call 2不仅在 clang 上编译失败,而且在 gcc 和 msvc 上编译失败。

call 1从标准的角度来看,编译成功和call 2失败是否正确?为什么?

注意:我知道我可以通过std::enable_if在第一个重载中添加 a 来解决该错误foo,但我想了解为什么call 1可以但call 2不行。

Yak*_*ont 5

替换失败不属于有限错误。如果故障发生在“直接上下文”之外,则故障很困难并且 SFINAE 无法阻止。

当你输入foo<Product>(),它首先创建一个重载集。该重载集包括 的两个重载foo。但是创建 foo 的第一个重载会导致static_assert直接上下文之外的失败,因此是一个硬错误。

当你输入foo()它还尝试创建一个过载集。两个模板均被考虑。它没有T被传入,因此它尝试T从参数中推断出 。

参数是一个Product&. T const&从 aProduct&中推导出T=Product const. Bar<T> const&从 a ...推导Product&无法推导 a T,因为Product它的任何基类都不是从 form 的模板生成的Bar<typename>

如果没有T推导,foo(Bar<T> const&)重载将被丢弃。无法T=Product推断,因此尝试实例化时不会foo(Bar<Product> const&)发生硬错误。

简而言之,对于调用 2,您手动强制一种Bar<Product>可能性存在。对于调用 1,编译器尝试推断模板参数,但从未到达那里。

作为一个思想实验,考虑将 的声明更改Product为:

class Product: public Bar<int> {
Run Code Online (Sandbox Code Playgroud)

并使其他一切保持不变。

现在

foo(*p)
Run Code Online (Sandbox Code Playgroud)

将产生 2 个要考虑的过载 - 一个

template<class T=Product const>
void foo(Product const&)
Run Code Online (Sandbox Code Playgroud)

和一个

template<class T=int>
void foo(Bar<int> const&)
Run Code Online (Sandbox Code Playgroud)

第二个是通过查看 的基类Product并找到Bar<int>. 在一种情况下T被推论出来,在另一种情况下被推论出来。Productint

但是,当您这样做时,foo<Product>您并不是依靠推导来查找T- 您正在手动设置它。没有任何扣除可做。