g++与clang++模板初始化行为差异原因及代码修复方案
问题分析与修复:g++/clang++下operator<<模板的ADL差异
报错原因
问题出在模板函数的声明顺序与依赖查找(ADL)的严格性差异:
- 你先定义了
vector<T>的operator<<模板,之后才定义array<T,C>的operator<<模板。 - 当实例化
operator<<<vector<array<int,2>>>(打印外层vector)时,代码里需要调用out << vec[i],这里的vec[i]是array<int,2>类型。 - 根据C++标准,模板实例化时,依赖模板参数的函数调用会先通过参数依赖查找(ADL) 匹配函数,但我们自定义的
operator<<不在std命名空间(array属于std,但我们的运算符是全局的),所以ADL找不到。 - 此时编译器会检查模板定义点是否有可见声明:
vector的operator<<定义时,array的operator<<还没声明,clang严格遵循标准报错;而g做了非标准的宽松处理,允许查找模板实例化点的声明,所以能编译通过。
修复方案
两种简单修复方式任选其一即可:
方案1:前置声明array的operator<<模板
在定义vector的operator<<之前,先声明array的operator<<模板,让编译器处理vector模板时知晓该函数存在:
#include <iostream> #include <vector> #include <array> // 前置声明array的operator<<模板 template <class T, std::size_t C> std::ostream &operator<<(std::ostream &out, const std::array<T, C> &arr); // 定义vector的operator<< 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() << ']'; } // 定义array的operator<< 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; }
方案2:调换两个operator<<的定义顺序
直接先定义array的operator<<,再定义vector的operator<<,确保vector模板定义时,array的运算符已经可见:
#include <iostream> #include <vector> #include <array> // 先定义array的operator<< 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() << '>'; } // 再定义vector的operator<< 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() << ']'; } #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; }
额外处理C++17警告
clang提示的折叠表达式是C17特性,编译时加上-std=c++17选项即可消除警告,比如:
clang++ -std=c++17 source.cpp -o output g++ -std=c++17 source.cpp -o output
内容的提问来源于stack exchange,提问作者Liu Weibo
相关产品推荐
相关产品推荐

