没有前向声明的嵌套函数模板实例化可以在 GCC 上编译,但不能在 clang 上编译

Posted

技术标签:

【中文标题】没有前向声明的嵌套函数模板实例化可以在 GCC 上编译,但不能在 clang 上编译【英文标题】:Nested function template instantiation without forward declaration compiles on GCC but not on clang 【发布时间】:2021-05-03 13:26:33 【问题描述】:

以下内容不能在 clang 中编译,但在 GCC (godbolt) 中可以:

template <typename K, typename V>
std::ostream& operator<< (std::ostream& o, const std::map<K, V>& map)

    const char* sep = "";
    for (const auto& x : map)
    
        o << sep << "" << x.first << ", " << x.second << "";
        sep = ", ";
    
    return o << "";


template <typename T>
std::ostream& operator<< (std::ostream& o, const std::vector<T>& vec)

    const char* sep = "";
    for (const auto& x : vec)
    
        o << sep << x;
        sep = ", ";
    
    return o << "";


// Usage

int main()

    std::map<int, std::vector<int>> t = 1, 2, 3, 4, 5;
    std::cout << t << std::endl;
    return 0;

谁是对的?

顺便说一句,我知道它是 UB,但是将两个模板定义放在 namespace std 中也会使代码在 clang 上编译。

代码借自Is it possible to define operator<< for templated types?

【问题讨论】:

如果你只是交换operator&lt;&lt;重载的顺序,它应该可以编译。 @TedLyngmo 是的,但是如果涉及到地图的矢量,那么更改顺序会破坏它^^ @Fareanor 当然 - 在这种情况下,必须转发声明。 template &lt;typename T&gt; std::ostream&amp; operator&lt;&lt;(std::ostream&amp; o, const std::vector&lt;T&gt;&amp; vec); @TedLyngmo 同意,我说是因为在链接的问题中,OP 不想转发声明任何内容。 @Fareanor 啊,我明白了(我没有阅读借用代码的链接)。 【参考方案1】:

Clang 是对的。您应该将operator&lt;&lt;std::vector 的声明移到operator&lt;&lt;std::map 的定义之前。

在unqualified name lookup,

(强调我的)

对于模板定义中使用的依赖名称,查找会推迟到知道模板参数为止,此时 ADL 会检查从模板定义上下文以及模板实例化上下文中可见的函数声明 with external linkage (until C++11) , 虽然非 ADL 查找仅检查从模板定义上下文可见的函数声明 with external linkage (until C++11)(换句话说,在模板定义之后添加新函数声明不会使其可见,除非通过 ADL) .

【讨论】:

我想知道造成这种差异的原因是什么 @Danra 关于 Clang 和 Gcc 之间区别的基本原理? ADL 和非 ADL 查找检查的声明之间 @Danra Non-ADL 在使用声明之前检查声明,这与非模板函数的工作方式相同。 ADL 检查声明用作参数的类的名称空间。在这种情况下,它们都找不到,因为std::vectoroperator&lt;&lt;std::map 的一个之后声明,那么它是不可见的,并且std::vector 在命名空间std 中声明,但operator&lt;&lt; 在全局命名空间中声明.

以上是关于没有前向声明的嵌套函数模板实例化可以在 GCC 上编译,但不能在 clang 上编译的主要内容,如果未能解决你的问题,请参考以下文章

模板方法何时可以使用稍后定义的函数,而无需前向声明?

可变参数模板错误:“在实例化中”(gcc 9.2)

为啥我不能从 gcc 中的前身模板化成员函数访问祖先方法?

C++中,怎么将函数模板的声明和定义分开写?

有没有办法避免警告/错误模板实例化回溯?

C++ 中嵌套类型/类的前向声明