这有什么问题:
#include <type_traits>
struct A;
template<typename T>
struct B
{
template<typename=std::enable_if<std::is_copy_constructible<T>::value>>
void f1() {}
};
template<typename T>
struct C {};
// Type your code here, or load an example.
int main() {
// Following fails
B<A> b;
// Could use this:
// b.f1<C>();
// This complies
C<A> c;
return 0;
}
/* This to be in or not doesn't make a difference
struct A
{};
*/
我在这里尝试了这个:https://godbolt.org/z/NkL44s使用不同的编译器:
那么为什么最近的编译器拒绝这一点呢?实例化
B<A>
时,尚不清楚将以哪种形式使用 f1
或是否会使用它。那么为什么编译器会抱怨它呢? f1
成员模板函数不是应该只有真正使用时才检查吗?
编辑:
正如评论中提到的,我在上面的代码中犯了一个无意的错误:
std::enable_if
应该是std::enable_if_t
,就像在这个更正的游乐场中一样:https://godbolt.org/z/cyuB3d
这改变了编译器无错误地传递此代码的情况:
但是,问题仍然存在:为什么从未使用过的函数的默认模板参数会导致编译失败?
原因是
std::is_constructible
需要完整的类型:(表42)
模板
template <class T> struct is_copy_constructible;
前提条件
应为完整类型、cv void 或未知边界的数组。T
未能满足库“应”的要求会导致未定义的行为。
来自 cppreference 于
is_copy_constructible<T>
:
T 应是完整类型、(可能是 cv 限定的)void 或未知边界的数组。否则,行为是未定义的。
所以看起来你在旧的编译器版本中只有普通的 UB,而新的编译器已经足够好告诉你
A
必须是一个完整的类型。
请注意,在 UB 存在的情况下,编译器不需要发出错误,但他们可以这样做,这是一件好事。
实例化类模板
B<A>
时,编译器实例化声明,但不实例化B<A>::f1
的定义:
类模板特化的隐式实例化导致
- 声明的隐式实例化,但不是定义、非删除类成员函数、成员类、作用域成员枚举、静态数据成员、成员模板和朋友的隐式实例化;和
- [...]
类模板特化的隐式实例化不会导致类成员函数的默认参数或 noexcept 说明符的隐式实例化。
然而,这仅仅意味着函数体没有被实例化;剩下的就是。 如果
f1
有默认参数,则不会实例化这些参数,但 std::enable_if
是 默认模板参数,因此此例外不适用。
以下内容被实例化:
template<typename = std::enable_if<std::is_copy_constructible<A>::value>> void f1() {}
std::is_copy_constructible
需要:
应为完整类型,cvT
,或未知边界的数组。void
std::copy_constructible
概念:
void f1() requires std::copy_constructible<T> {}
在 C++17 中:
template<typename U = T>
std::enable_if_t<std::is_copy_constructible_v<U>> f1() {}
这是有效的,因为
std::enable_if_t
依赖于当前函数模板的模板参数,因此 SFINAE 可以按预期发生。
请参阅 std::enable_if 以有条件地编译成员函数了解更多策略。