C++17中如何实现模板基类控制台打印兼容无<<运算符类型
问题描述
我有如下基础模板类:
template<typename Type> struct InputBase { virtual void DisplaySingleResult(Type const& res) const { std::cout << res << std::endl; } };
下方派生类代码无法通过编译,原因是vector未重载<<运算符:
struct Input : InputBase<std::vector<std::vector<int>>> { void DisplaySingleResult(std::vector<std::vector<int>> const& results) const override { for (std::vector<int> const& result : results) { std::cout << "[" << result[0] << "," << result[1] << "]"; } std::cout << std::endl; } };
编译阶段,InputBase::DisplaySingleResult方法会因模板参数为vector类型编译失败。
一种临时规避方案是将基类的虚方法InputBase::DisplaySingleResult改为纯虚函数,但该方案需要所有派生类重写该方法,重复实现已有基础类型的输出逻辑。
需求为:基类中保留签名完全一致的DisplaySingleResult默认实现,无需为支持<<运算符的类型重写方法,同时适配不支持<<运算符的自定义类型派生类,基于C++17标准实现。
初步思路:在InputBase::DisplaySingleResult方法中,先检测模板类型Type是否实现了<<运算符,再执行对应输出逻辑。
实现方案
C++17标准下可以通过SFINAE类型检测+if constexpr编译期分支实现,核心逻辑是编译期自动判断类型是否支持流输出,条件不成立的分支不会被实例化,从根源避免编译错误。
第一步:实现流输出能力检测Trait
先写编译期检测工具,判断给定类型是否支持通过<<运算符输出到std::ostream:
#include <iostream> #include <type_traits> #include <vector> #include <string> // 检测类型T是否支持operator<<输出到std::ostream template<typename T, typename = void> struct is_ostream_printable : std::false_type {}; template<typename T> struct is_ostream_printable<T, std::void_t<decltype(std::declval<std::ostream&>() << std::declval<const T&>())>> : std::true_type {}; template<typename T> constexpr bool is_ostream_printable_v = is_ostream_printable<T>::value;
第二步:修改基类默认实现
将基类的DisplaySingleResult方法改为编译期分支逻辑,支持流输出的类型走原有默认输出,不支持的分支不实例化流输出代码:
template<typename Type> struct InputBase { virtual void DisplaySingleResult(Type const& res) const { if constexpr (is_ostream_printable_v<Type>) { // 支持<<的类型直接走默认输出逻辑 std::cout << res << std::endl; } else { // 未重写方法的非打印类型走到这里时,抛出清晰的编译错误 static_assert(is_ostream_printable_v<Type>, "当前类型未重载<<运算符,也未重写DisplaySingleResult方法,请实现对应输出逻辑"); } } };
方案效果
- 对于
int、std::string等原生支持<<的类型,派生类无需重写任何方法,直接使用基类默认实现即可 - 对于
std::vector等不支持默认<<的类型,只要派生类重写DisplaySingleResult实现自定义输出逻辑,基类中无效的流输出代码不会被实例化,不会触发编译错误 - 若派生类没有重写方法、且类型不支持
<<,会触发清晰的编译期报错,提示开发者实现对应逻辑
测试用例如下,可直接编译运行:
// 无需重写方法,直接使用基类默认实现 struct IntInput : InputBase<int> {}; struct StringInput : InputBase<std::string> {}; // 自定义vector<vector<int>>的输出逻辑,和原有写法完全一致 struct VecInput : InputBase<std::vector<std::vector<int>>> { void DisplaySingleResult(std::vector<std::vector<int>> const& results) const override { for (std::vector<int> const& result : results) { std::cout << "[" << result[0] << "," << result[1] << "]"; } std::cout << std::endl; } }; int main() { IntInput{}.DisplaySingleResult(42); // 输出:42 StringInput{}.DisplaySingleResult("test"); // 输出:test std::vector<std::vector<int>> data = {{1,2}, {3,4}}; VecInput{}.DisplaySingleResult(data); // 输出:[1,2][3,4] return 0; }
可选优化
如果需要给容器类提供通用默认输出,可以在else分支增加可迭代类型检测,自动遍历容器元素输出,进一步减少派生类重写的工作量。
内容的提问来源于stack exchange,提问作者Brainless
相关产品推荐
相关产品推荐

