static_assert为何破坏模板替换?C++14编译差异解析
为什么显式指定模板参数的
foo调用编译失败,而隐式推导的可以? 代码示例
#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: 编译成功 foo<Product>(*p); // call 2: 编译失败 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.
问题
原本认为foo(*p)和foo<Product>(*p)都应正常编译,但后者在Clang、GCC及MSVC中均编译失败。从C++14标准的角度来看,这种行为是否符合规范?原因是什么?
注:已知可通过为第一个foo重载添加std::enable_if规避错误,但希望理解两种调用行为差异的根本原因。
解答
1. 隐式推导调用(call1: foo(*p))的行为
编译器会对两个foo重载执行模板参数推导和重载决议:
- 对于第一个重载
foo(Bar<T> const&):编译器尝试将实参*p(类型为Product)推导为Bar<T>类型,显然推导失败,因此这个重载会被**SFINAE(替换失败并非错误)**规则排除,不参与后续的重载决议。 - 对于第二个重载
foo(T const&):编译器成功推导出T=Product,该重载与实参完全匹配,因此被选中执行。整个过程中Bar<Product>从未被实例化,所以static_assert不会触发,编译通过。
2. 显式指定模板参数调用(call2: foo<Product>(*p))的行为
显式指定T=Product后,编译器不再执行模板参数推导,而是直接代入参数检查两个重载的可行性:
- 对于第一个重载
foo(Bar<Product> const&):为了检查实参Product const&能否隐式转换为Bar<Product> const&,编译器必须先实例化Bar<Product>(因为需要知道Bar<Product>的构造函数是否支持这种转换)。 - 实例化
Bar<Product>时,触发类内的static_assert(std::is_destructible<Product>::value)。根据C++14标准,std::is_destructible<T>的判断依据是:在当前上下文(这里是main函数)中,能否合法调用T的析构函数。由于Product的析构函数是private的,main无法直接访问,因此std::is_destructible<Product>::value为false,静态断言触发硬错误,导致编译失败。 - 这里的关键是:SFINAE仅在模板参数推导阶段生效,显式指定参数后没有推导过程,直接进入模板实例化,实例化中的静态断言失败不属于SFINAE范畴,会直接导致编译错误。
3. 关于std::is_destructible<Product>的判断依据
C++14标准中,std::is_destructible<T>的结果为true当且仅当:
- 如果
T是对象类型,那么表达式delete static_cast<T*>(nullptr)在当前上下文是良构的(即析构函数可访问且可调用); - 如果
T是引用类型,则结果等同于std::is_destructible<remove_reference_t<T>>; - 其他类型(如void)结果为
true。
由于Product的析构函数是private的,在main的上下文无法直接调用,因此std::is_destructible<Product>::value为false。
内容的提问来源于stack exchange,提问作者Paulo
相关产品推荐
相关产品推荐

