为什么 static_assert 会破坏替换?

问题描述 投票:0回答:2

请考虑以下 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;
}

还有来自 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.

我的理解是

call 1
call 2
都应该可以正常编译,但是
call 2
不仅无法在 clang 上编译,而且无法在 gcc 和 msvc 上编译。

从标准角度看,

call 1
编译成功而
call 2
编译失败,这样正确吗?为什么?

注意:我知道我可以通过在

std::enable_if
的第一个重载中添加
foo
来解决该错误,但我想了解为什么
call 1
可以,但
call 2
不行。

c++ sfinae static-assert
2个回答
0
投票

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

当您输入

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

当您输入

foo(
?
)
时,它还会尝试创建一个重载集。两个模板均被考虑。它没有传入
T
,因此它尝试从参数中推导出
T

论证是

Product&
。从
T const&
推出
Product&
会产生
T=Product const
。从
Bar<T> const&
推导出
Product&
... 无法推导出
T
,因为
Product
及其任何基类都不是从
Bar<typename>
形式的模板生成的。

如果没有推导出

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

简而言之,对于调用 2,您手动强制存在

Bar<Product>
可能性。对于调用 1,编译器尝试推断模板参数,但永远无法实现。

作为一个思想实验,考虑将

Product
的声明更改为:

class Product: public Bar<int> {

其他一切保持不变。

现在

foo(*p)

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

template<class T=Product const>
void foo(Product const&)

还有一个

template<class T=int>
void foo(Bar<int> const&)

第二个是通过查看

Product
的基类并找到
Bar<int>
生成的。在一种情况下,
T
被推导为
Product
,在另一种情况下,
int

但是当你这样做

foo<Product>
时,你并不是依靠演绎来找到
T
- 你正在手动设置它。没有任何扣除可做。


0
投票

foo(*p)
中,由于
Product
不是
Bar<T>
,因此没有实例化
void foo(Bar<T> const &)

唯一可能的过载是

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

foo<Product>(*p)
中,您指定模板参数,因此有以下候选者

  • foo<Product>(const Bar<Product>&)
  • foo<Product>(const Product&)

要知道

Product
是否可以转换为
Bar<Product>
,您必须实例化它,从而产生硬错误。

编译器可能会实例化该模板,即使它不会被重载解析所采用。

foo(可能)实例化 Bar,这可能是匹配的(但事实并非如此)。但是 Bar 会导致硬错误。

© www.soinside.com 2019 - 2024. All rights reserved.