c++ 未使用的成员模板函数和成员函数声明的隐式示例化导致类模板示例化失败的原因

z4iuyo4d  于 2024-01-09  发布在  其他
关注(0)|答案(3)|浏览(166)

这有什么错:

  1. #include <type_traits>
  2. struct A;
  3. template<typename T>
  4. struct B
  5. {
  6. template<typename=std::enable_if<std::is_copy_constructible<T>::value>>
  7. void f1() {}
  8. };
  9. template<typename T>
  10. struct C {};
  11. // Type your code here, or load an example.
  12. int main() {
  13. // Following fails
  14. B<A> b;
  15. // Could use this:
  16. // b.f1<C>();
  17. // This complies
  18. C<A> c;
  19. return 0;
  20. }
  21. /* This to be in or not doesn't make a difference
  22. struct A
  23. {};
  24. */

字符串
我在这里尝试了这个: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(内部测试):失败

然而,问题仍然存在:为什么一个从未使用过的函数的默认模板参数会导致编译失败?

mcvgt66p

mcvgt66p1#

原因是std::is_constructible需要完整的类型:(表42)

模板

  1. template <class T>
  2. struct is_­copy_­constructible;

字符串

前提条件

T应该是一个完整的类型,cv void,或者是一个未知边界的数组。
不满足库“应该”的要求会导致未定义的行为。

展开查看全部
xv8emn3q

xv8emn3q2#

is_copy_constructible<T>上的cppreference
T应该是一个完整的类型,(可能是cv限定的)void,或者是一个未知边界的数组。否则,行为是未定义的。
因此,在旧的编译器版本中,你似乎只有普通的UB,而新的编译器版本很好地告诉你,A必须是一个完整的类型。
请注意,在UB存在的情况下,编译器不需要发布错误,但他们可能会这样做,这是一件好事。

cuxqih21

cuxqih213#

当示例化类模板B<A>时,编译器示例化声明,但不示例化B<A>::f1的定义:
类模板专门化的隐式示例化导致

*声明的隐式示例化,而不是定义,非删除类成员函数,成员类,作用域成员枚举,静态数据成员,成员模板和朋友;以及

  • [...]

类模板专门化的隐式示例化不会导致类成员函数的默认参数或noexcept说明符的隐式示例化。

  • [温度 Jmeter ] p3
    如果f1有默认参数,这些参数不会被示例化,但是std::enable_if是一个 default template argument,所以这个异常不适用。
    以下代码将被示例化:
  1. template<typename = std::enable_if<std::is_copy_constructible<A>::value>>
  2. void f1() {}

字符串
默认的模板参数是格式错误的,因为std::is_copy_constructible需要:
T应该是一个完整的类型,cvvoid,或者是一个未知边界的数组。

溶液

在C++20中,使用结尾的requires-clause和std::copy_constructible概念:

  1. void f1() requires std::copy_constructible<T> {}


在C++17中:

  1. template<typename U = T>
  2. std::enable_if_t<std::is_copy_constructible_v<U>> f1() {}


这是因为std::enable_if_t依赖于当前函数模板的模板参数,所以SFINAE可以按预期发生。
更多策略请参见std::enable_if to conditionally compile a member function

展开查看全部

相关问题