C++跨头文件模板问题:Tmp类迭代器模板无法识别外部类
问题描述
我在给自定义包装类Tmp写模板时碰上个问题:
Tmp已经给部分基本类型实现了operator<<,像Foo这类需要的类,我也单独写了对应的运算符重载。- 我想写个模板,只要类型的
operator<<已经实现,就能处理可迭代类型(比如容器)。现在这个模板对基本类型管用,但碰到其他文件里实现的类就失效了。 - 我知道是编译器找不到运算符的候选实现,除了把所有类和模板塞同一个头文件,还有别的办法让编译器找到这些实现吗?
最小可复现示例(MWE)
// tmp.h #include <iterator> #include <iostream> class Tmp { friend Tmp& operator<<(Tmp& os, int i); template<typename Iter> friend Tmp& operator<<(Tmp& os, std::pair<Iter, Iter> iter) { for (auto it = iter.first; it != iter.second; ++it) { os << *it; } return os; } }; // tmp.cpp #include "tmp.h" Tmp& operator<<(Tmp& os, int i) { std::cout << i << '\n'; return os; } // foo.h class Tmp; // 前置声明Tmp类 class Foo { friend Tmp& operator<<(Tmp& os, const Foo&); }; // foo.cpp #include "foo.h" #include "tmp.h" Tmp& operator<<(Tmp& os, const Foo&) { std::cout << "Foo\n"; return os; } // main.cpp #include "tmp.h" #include "foo.h" #include <vector> int main() { std::vector vec{1, 2, 3, 4, 5}; Tmp a; a << 0; a << std::pair{vec.begin(), vec.end()}; Foo foo; a << foo; std::vector foo_vec{Foo(), Foo(), Foo()}; // a << std::pair{foo_vec.begin(), foo_vec.end()}; // <- 这行模板处理不了,编译器找不到对应的operator<< return 0; }
解决办法
问题根源在于模板实例化时的名字查找规则:模板里的os << *it被实例化时,编译器只会找模板定义时可见的名字,以及通过**依赖参数查找(ADL)**找到的名字。给你几个可行的方案:
1. 在头文件里显式声明运算符
Foo的operator<<虽然是友元函数,但只在类内部声明的话,ADL虽然能找到,但显式在头文件里声明会更稳妥。修改foo.h:
// foo.h class Tmp; class Foo; // 显式声明这个运算符 Tmp& operator<<(Tmp& os, const Foo&); class Foo { friend Tmp& operator<<(Tmp& os, const Foo&); };
这样main.cpp包含foo.h后,模板实例化时就能看到这个声明,编译器自然能找到对应的实现。
2. 把运算符和类放在同一个命名空间里
如果给Foo加个命名空间,把对应的operator<<也放进同一个命名空间,ADL会自动去这个命名空间里找运算符,哪怕没有外部声明也能生效:
// foo.h namespace myns { class Tmp; class Foo { friend Tmp& operator<<(Tmp& os, const Foo&); }; } // namespace myns // foo.cpp #include "foo.h" #include "tmp.h" namespace myns { Tmp& operator<<(Tmp& os, const Foo&) { std::cout << "Foo\n"; return os; } } // namespace myns
这种方式不用额外加声明,靠ADL就能让模板找到运算符。
3. 写个通用的可迭代类型模板(可选)
如果你想直接支持std::vector这类容器,不用手动构造std::pair,可以写个自动检测可迭代类型的模板,同时保证ADL能正常工作:
// tmp.h #include <iterator> #include <iostream> #include <type_traits> // 辅助模板:检测类型是否可迭代 template<typename T, typename = void> struct is_iterable : std::false_type {}; template<typename T> struct is_iterable<T, std::void_t<decltype(std::begin(std::declval<T>())), decltype(std::end(std::declval<T>()))>> : std::true_type {}; class Tmp { friend Tmp& operator<<(Tmp& os, int i); // 只对可迭代类型启用的模板 template<typename Container, std::enable_if_t<is_iterable<Container>::value, int> = 0> friend Tmp& operator<<(Tmp& os, const Container& cont) { for (const auto& elem : cont) { os << elem; // 这里ADL会自动找elem类型对应的operator<< } return os; } };
改完之后直接写a << foo_vec;就行,不用再搞std::pair那套。
重点提醒
- 模板里调用运算符时,如果依赖ADL,一定要保证运算符和右操作数的类型在同一个命名空间里。
- 运算符的实现放cpp文件没问题,但必须在头文件里声明,不然模板实例化时编译器看不到,肯定报错。
内容的提问来源于stack exchange,提问作者Fanteria
相关产品推荐
相关产品推荐

