如何在未包含<fstream>时仍能编译含dump方法的类A?
示例场景
现有如下C++类:
class A{ double a; void dump(){ std::ofstream ooo("dump.txt"); ooo << a << "\n"; ooo.close(); } };
该类的大量使用者不会调用dump方法,且无法引入<fstream>头文件,需要实现:当未引入<fstream>时,类A仍能正常编译(效果等价于省略void dump()方法)。
核心问题
解决该场景的最佳实践是什么?
约束条件
- 不允许修改
dump方法的签名; - 除非
<fstream>按ISO标准定义了对应宏,否则不能使用#ifdef这类预处理指令; - 类A是多个其他类的父类,若对A进行模板特化,所有派生类都需模板化继承,该方案不可行。
尝试过的方案
曾尝试基于std::ofstream可实例化性的依赖定义方案,但无论哪种情况都必须编写方法定义,最终导致编译错误。受限于C++的语法特性,我认为必须在某个阶段明确方法定义,而模板定义最终会被实例化,仍会产生无效定义,因此需要了解对应的语法或设计范式。
备注
这是我首次遇到此类问题,在解释型语言中,定义仅在被使用时才会生效是常态,但C++作为编译型语言需要不同的处理方式。
解决方案
方案1:C++20 约束(requires子句)
利用C++20的概念约束特性,让dump方法仅在std::ofstream类型存在时才被声明:
#include <type_traits> class A{ double a; public: void dump() requires requires { typename std::ofstream; } { std::ofstream ooo("dump.txt"); ooo << a << "\n"; ooo.close(); } };
当<fstream>未被引入时,std::ofstream类型不存在,requires约束不满足,dump方法会被编译器视为不存在,类A可以正常编译;当引入<fstream>后,dump方法正常可用,且签名完全符合要求。
方案2:C11/C17 SFINAE 兼容方案
如果需要兼容C11或C17版本,可以借助SFINAE(替换失败并非错误)特性,结合成员模板实现(无需将类A模板化):
#include <type_traits> class A{ double a; public: // 当std::ofstream不存在时,该模板无法实例化(相当于dump方法不存在) template<typename = typename std::enable_if<false>::type> void dump() = delete; // 仅当std::ofstream存在时,该模板才会生效 template<typename = typename std::enable_if<true, typename std::decay<decltype(std::ofstream{})>::type>::type> void dump(){ std::ofstream ooo("dump.txt"); ooo << a << "\n"; ooo.close(); } };
当<fstream>未引入时,第二个模板的decltype(std::ofstream{})会触发替换失败,此时只有被delete的第一个dump模板存在,但由于模板参数默认值的enable_if<false>限制,该模板无法被实例化,相当于dump方法不存在;当引入<fstream>后,第二个模板生效,dump方法可以正常调用,签名也符合要求。
方案合规性说明
两种方案均满足所有约束条件:
- 未修改
dump方法的对外签名(调用时无需额外模板参数); - 未使用依赖
<fstream>非标准宏的预处理指令; - 类A无需模板化,派生类可正常继承,无需修改实现。
内容的提问来源于stack exchange,提问作者user23311233

