为什么 G++ 和 Clang++ 之间的模板初始化行为不同?

why there's a different behavior in template initialization between g++ and clang++?

提问人:Liu Weibo 提问时间:11/7/2023 最后编辑:Liu Weibo 更新时间:11/7/2023 访问量:63

问:

#include <iostream>
#include <vector>
#include <array>

template <typename T> std::ostream &operator<<(std::ostream &out, const std::vector<T> &vec)
{
    if (vec.empty()) {
        out << "[]";
        return out;
    }
    out << '[';
    for (int i = 0; i < vec.size() - 1; i++) {
        out << vec[i] << ", ";
    }
    return out << vec.back() << ']';
}
template <class T, std::size_t C> std::ostream &operator<<(std::ostream &out, const std::array<T, C> &arr)
{
    if (arr.empty()) {
        out << "<>";
        return out;
    }
    out << '<';
    for (int i = 0; i < arr.size() - 1; i++) {
        out << arr[i] << ", ";
    }
    return out << arr.back() << '>';
}

#include <string>
#define dbg(...) logger(__LINE__, #__VA_ARGS__, __VA_ARGS__)
template <typename... Args> void logger(int line, std::string vars, Args &&...values)
{
    std::cerr << "Ln " << line << ": " << vars << " = ";
    std::string delim = "";
    (..., (std::cerr << delim << values, delim = ", "));
    std::cerr << std::endl, std::cerr.flush();
}

using namespace std;

int main()
{
    using a2i = array<int, 2>;
    using va2i = vector<a2i>;
    using a4v = array<va2i, 4>;
    
    a4v a4;
    a4[0].push_back({3, 5});
    a4[1].push_back({9, 8}), a4[1].push_back({1, 2});
    
    dbg(a4);

    return 0;
}

编译上面的代码,你会得到不同的结果。https://rextester.com/l/cpp_online_compiler_clanghttps://rextester.com/l/cpp_online_compiler_gcc

g++ one 将按预期编译和工作,而 clang++ 抱怨如下:

96501129/source.cpp:36:6: warning: pack fold expression is a C++17 extension [-Wc++17-extensions]
    (..., (std::cerr << delim << values, delim = ", "));
     ^
96501129/source.cpp:13:13: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
        out << vec[i] << ", ";
            ^
96501129/source.cpp:25:13: note: in instantiation of function template specialization 'operator<<<std::__1::array<int, 2> >' requested here
        out << arr[i] << ", ";
            ^
96501129/source.cpp:36:31: note: in instantiation of function template specialization 'operator<<<std::__1::vector<std::__1::array<int, 2>, std::__1::allocator<std::__1::array<int, 2> > >, 4>' requested here
    (..., (std::cerr << delim << values, delim = ", "));
                              ^
96501129/source.cpp:52:5: note: in instantiation of function template specialization 'logger<std::__1::array<std::__1::vector<std::__1::array<int, 2>, std::__1::allocator<std::__1::array<int, 2> > >, 4> &>' requested here
    dbg(a4);
    ^
96501129/source.cpp:31:18: note: expanded from macro 'dbg'
#define dbg(...) logger(__LINE__, #__VA_ARGS__, __VA_ARGS__)
                 ^
96501129/source.cpp:17:49: note: 'operator<<' should be declared prior to the call site
template <class T, std::size_t C> std::ostream &operator<<(std::ostream &out, const std::array<T, C> &arr)
                                                ^
96501129/source.cpp:15:16: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
    return out << vec.back() << ']';
               ^
96501129/source.cpp:17:49: note: 'operator<<' should be declared prior to the call site
template <class T, std::size_t C> std::ostream &operator<<(std::ostream &out, const std::array<T, C> &arr)
                                                ^
1 warning and 2 errors generated.

似乎 clang 无法在最内层中以 as 实例类型初始化对象上的运算符模板。<<ostreamarray<int, 2>array

大小为 4 的最外数组和数组的向量被成功呈现,而最里面的数组则没有在这种类型的 上渲染。array<vector<array<int, 2>>, 4>

我想知道这个抱怨的原因是什么,以及如何修复代码以使其在 gcc 和 clang 上工作

C++ 模板

评论

1赞 HolyBlackCat 11/7/2023
您正在过时的 GCC 上进行测试。GCC 12 及更高版本也会拒绝您的代码。您来自 Clang 的错误消息似乎被缩短了,我在这里得到了一个更详细的错误,它解释了这个问题。最终,运算符必须在 ADL 可以找到的地方重载(即通常与其中一个操作数位于同一命名空间中),这意味着您不能理智地重载标准类的运算符,因为您不允许将运算符添加到 .namespace std
1赞 n. m. could be an AI 11/7/2023
@HolyBlackCat 你可以为标准容器超载,你只需要跳过一些(不是很有挑战性)的箍<<
2赞 HolyBlackCat 11/7/2023
@n.m.couldbeanAI 呃。我想这有效,但它迫使您将这些重载烘烤到记录器中。不能从客户端代码中添加更多无 ADL 的重载。我会选择类似专业特征的东西。template <typename T> struct PrintToStream{...};
1赞 n. m. could be an AI 11/7/2023
@LiuWeibo 它们可以相互引用,例如,当您有一个向量数组或数组的向量时。因此,两个实现都应该看到彼此的声明。
1赞 HolyBlackCat 11/7/2023
像这样的东西:godbolt.org/z/cdzYErc7q 匆匆忙忙,但它似乎有效。

答: 暂无答案