这有什么错:
#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与不同的编译器:
- x86-64 gcc 9.2:编译
- x86-64 gcc(中继):失败
- x86-64 clang 6.0.0:编译
- x86-64 clang 7.0.0及更高版本:失败
- x64 msvc v19.22:编译
- x64 msvc v19.23(内部测试):失败
那么为什么最近的编译器拒绝这样做呢?当示例化B<A>
时,并不清楚f1
将以何种形式使用,或者它是否会被使用。那么为什么编译器会抱怨呢?难道不应该只在f1
成员模板函数被真正使用时才检查它吗?
编辑:
正如在评论中提到的,我在上面的代码中犯了一个无意的错误:std::enable_if
应该是std::enable_if_t
,就像在这个更正的操场中一样:https://godbolt.org/z/cyuB3d
这改变了编译器无错误地传递此代码的画面:
- gcc:失败
- clang:失败
- x64 msvc v19.22:编译
- x64 msvc v19.23(内部测试):失败
然而,问题仍然存在:为什么一个从未使用过的函数的默认模板参数会导致编译失败?
3条答案
按热度按时间mcvgt66p1#
原因是
std::is_constructible
需要完整的类型:(表42)模板
字符串
前提条件
T
应该是一个完整的类型,cv void,或者是一个未知边界的数组。不满足库“应该”的要求会导致未定义的行为。
xv8emn3q2#
从
is_copy_constructible<T>
上的cppreference:T应该是一个完整的类型,(可能是cv限定的)void,或者是一个未知边界的数组。否则,行为是未定义的。
因此,在旧的编译器版本中,你似乎只有普通的UB,而新的编译器版本很好地告诉你,
A
必须是一个完整的类型。请注意,在UB存在的情况下,编译器不需要发布错误,但他们可能会这样做,这是一件好事。
cuxqih213#
当示例化类模板
B<A>
时,编译器示例化声明,但不示例化B<A>::f1
的定义:类模板专门化的隐式示例化导致
*声明的隐式示例化,而不是定义,非删除类成员函数,成员类,作用域成员枚举,静态数据成员,成员模板和朋友;以及
类模板专门化的隐式示例化不会导致类成员函数的默认参数或noexcept说明符的隐式示例化。
如果
f1
有默认参数,这些参数不会被示例化,但是std::enable_if
是一个 default template argument,所以这个异常不适用。以下代码将被示例化:
字符串
默认的模板参数是格式错误的,因为
std::is_copy_constructible
需要:T
应该是一个完整的类型,cvvoid
,或者是一个未知边界的数组。溶液
在C++20中,使用结尾的requires-clause和
std::copy_constructible
概念:型
在C++17中:
型
这是因为
std::enable_if_t
依赖于当前函数模板的模板参数,所以SFINAE可以按预期发生。更多策略请参见std::enable_if to conditionally compile a member function。