为啥未使用的成员模板函数的类模板实例化失败

Posted

技术标签:

【中文标题】为啥未使用的成员模板函数的类模板实例化失败【英文标题】:Why class template instantiation fails for unused member template function为什么未使用的成员模板函数的类模板实例化失败 【发布时间】:2019-10-10 10:05:54 【问题描述】:

这有什么问题:

#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&lt;A&gt; 时,不清楚f1 将以哪种形式使用,或者是否会使用它。那么为什么编译器会抱怨呢? f1成员模板函数不应该只在真正使用时才检查吗?


编辑: 正如 cmets 中提到的,我在上面的代码中犯了一个无意的错误:std::enable_if 应该是 std::enable_if_t,就像在这个更正的游乐场中一样:https://godbolt.org/z/cyuB3d

这改变了编译器无错误地传递此代码的情况:

gcc:失败 clang:失败 x64 msvc v19.22:编译 x64 msvc v19.23(内部测试):失败

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

【问题讨论】:

这是typename = std::enable_if_t&lt;...&gt;,而不是typename = std::enable_if&lt;...&gt; 您可以包含错误消息吗?所有编译器都以同样的方式抱怨吗? 简化案例:template&lt;bool = std::is_copy_constructible_v&lt;T&gt;&gt; void f1() 。该问题似乎与默认模板参数的解析时间有关。请注意,Tincomplete type 会导致 undefined behaior。我可以观察到的差异是由编译器检查类型完整性引起的。 @L.F.我觉得这里不重要。不应用 SFINAE,匿名模板参数也可以引用 std::enable_if 类型本身。 我在帖子中添加了一个段落,以回应正确发现的 std::enable_ifstd::enable_if_t 的误用。 【参考方案1】:

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

模板

template <class T>
struct is_­copy_­constructible;

前提条件

T 应为完整类型、cv void 或未知边界数组。

未能满足库“应”要求会导致未定义的行为。

【讨论】:

就像我的回答一样,您也错过了解释为什么方法中的错误在人们可以预期它实际上没有被实例化时很重要 @foreknownas_463035818 嗯,我也不确定。看来默认参数不应该被实例化... 奇怪的是,整个temp.inst 部分只提到了一次默认模板参数——在变量模板的上下文中。我在标准中找不到任何内容来指定何时应实例化默认模板参数。 @DanielLangr 是的,模板声明本身的实例化方式似乎没有明确说明。 无论如何,我相信在所有情况下,编译器都会实例化默认模板参数。唯一的区别是他们是否在std::is_copy_constructible 类型特征中检查T 开始完整类型。例如,这项检查是在 2019 年 5 月(即相对最近)添加到 libstdc++ 中的;见this commit,即these added lines。【参考方案2】:

来自cppreferenceis_copy_constructible&lt;T&gt;

T 应该是一个完整的类型,(可能是 cv 限定的)void,或者一个未知边界的数组。否则,行为未定义。

所以看起来你在旧的编译器版本中只有普通的 UB,而新的编译器很好地告诉你 A 必须是一个完整的类型。

请注意,在存在 UB 的情况下,编译器不需要发出错误,但他们可能会这样做,这是一件好事。

【讨论】:

以上是关于为啥未使用的成员模板函数的类模板实例化失败的主要内容,如果未能解决你的问题,请参考以下文章

C++ Primer 5th笔记(chap 16 模板和泛型编程)类模板成员函数的实例化

类模板中的static关键字

类和对象之模板

C++模板详解

C++ 模板详解(转)

C++模板