C++ std::derived_from解引用迭代器引用类型编译错误
问题背景
- 本帖包含较多代码,已移除与问题无关的代码及大部分函数文档,若相关内容有参考必要可随时提出。
- 正在实现
cu命名空间下的打印工具:print是顶层分发入口,println针对不同数据类型提供多个重载版本。 - 问题仅出现在打印
std::vector<cu::ptext>对象的场景,其余测试用例均正常编译运行。
相关实现代码
println.h
// All needed includes namespace cu { template<typename Dp> concept DefaultPrintable = requires(std::ostream& os, Dp ln) { os << ln; }; } #ifdef _WIN32 namespace cu { template<typename Po> concept PrettyTextObject = std::derived_from<Po, console::ptext> && requires(Po text) { text.display(); }; template<typename Ip> concept IteratorPrintable = !DefaultPrintable<Ip> && requires(Ip container) { std::begin(container); std::end(container); requires DefaultPrintable<decltype(*std::begin(container))> || PrettyTextObject<decltype(*std::begin(container))>; }; } #endif namespace cu { template<DefaultPrintable Dp> void println(Dp line) { std::cout << line; } } #ifdef _WIN32 namespace cu { template<PrettyTextObject Po> void println(const Po& pretty_text) { pretty_text.display(); } } #endif namespace cu { template<IteratorPrintable Ip> /** Allows iteratiable objects with well-formed std::begin() and std::end() to be printed, with each element being printed on its own line. THIS FUNCTION SHOULD NOT BE ACCESSED DIRECTLY. Instead, use cu::print() or cu::prompt(). @param container The container whose elements should be printed. */ void println(Ip container) { // Figure out size of container size_t elements_ct = 0; for (auto el = std::begin(container); el != std::end(container); ++el) elements_ct++; if (elements_ct > 0) { using ItTy = decltype(std::begin(container)); struct ContainerLoop { ItTy itr; size_t i; }; for (ContainerLoop cl{ std::begin(container), 0 }; cl.itr != std::end(container); ++cl.itr) { println(*cl.itr); if (cl.i++ < elements_ct - 1) std::cout << '\n'; } } } } namespace cu { template<typename Ty> concept Printable = requires(Ty ln) { println(ln); }; }
print.h
namespace cu { /** Null case for print() which flushes the stream. */ inline void print() { std::cout << std::flush; } template<Printable Pt, Printable... Pts> /** Prints all arguments to console on their own line. All arguments must be well-defined for cu::println([arg]). @param line Current object to print to console @param others All other objects to send through recursive function call */ void print(Pt line, Pts... others) { println(line); std::cout << '\n'; print(std::forward<Pts>(others)...); } }
测试驱动代码
cu::print("This is a test"); // 正常编译 std::vector<std::string> stest{ "This", "is", "test" }; cu::print(stest); // 正常编译 cu::print(cu::ptext("This is a test")); // 正常编译 std::vector<cu::ptext> ptest{ "This", "is", "test" }; cu::print(ptest); // 编译失败
编译报错信息
<source>:151:1: error: no matching function for call to 'print' cu::print(ptest); // <-- This line does not compile ^~~~~~~~~ <source>:133:10: note: candidate template ignored: constraints not satisfied [with Pt = std::vector<cu::ptext>, Pts = <>] void print(Pt line, Pts... others) ^ <source>:125:14: note: because 'std::vector<cu::ptext>' does not satisfy 'Printable' template<Printable Pt, Printable... Pts> ^ <source>:111:9: note: because 'println(ln)' would be invalid: no matching function for call to 'println' println(ln); ^ <source>:120:17: note: candidate function not viable: requires 0 arguments, but 1 was provided inline void print() ^ 1 error generated. Compiler returned: 1
核心报错点:
std::vector<cu::ptext>不满足Printable概念约束,无匹配的println重载- 无参
print重载仅支持0个入参,无法处理传入的1个参数
根因分析
问题出在两个实现缺陷:
- 类型检查未处理引用限定:
IteratorPrintable概念中,通过decltype(*std::begin(container))获取的元素类型是cu::ptext&(迭代器解引用返回引用),而PrettyTextObject概念内的std::derived_from要求传入的是纯类类型,不支持引用类型,导致PrettyTextObject<cu::ptext&>判定为false,std::vector<cu::ptext>无法满足IteratorPrintable约束,匹配不到对应的println容器重载。
这也是为什么std::vector<std::string>可以正常编译:DefaultPrintable的约束是os << ln,支持传入引用类型,不会触发该问题;单个cu::ptext传入时,模板参数推导为cu::ptext纯类型,也能正常匹配PrettyTextObject重载。 - 平台宏块划分错误:
IteratorPrintable概念定义被包裹在#ifdef _WIN32块内,但接收IteratorPrintable类型的println容器重载却在宏块外,非Windows环境下编译会直接报「IteratorPrintable未定义」错误。
修复方案
- 调整
IteratorPrintable概念的位置,移出平台宏块,非Windows环境下跳过PrettyTextObject分支检查即可。 - 获取容器元素类型时,使用
std::remove_cvref_t去掉引用和const/volatile限定,得到纯值类型再做概念检查,需要提前包含<type_traits>头文件。
修复后的IteratorPrintable概念示例:
// 移出_WIN32宏块,全局可用 namespace cu { template<typename Ip> concept IteratorPrintable = !DefaultPrintable<Ip> && requires(Ip container) { std::begin(container); std::end(container); // 先去除引用和cv限定,拿到纯元素类型 using ElTy = std::remove_cvref_t<decltype(*std::begin(container))>; requires DefaultPrintable<ElTy> #ifdef _WIN32 || PrettyTextObject<ElTy> #endif ; }; }
如果使用的C版本不支持C20的std::remove_cvref_t,可以替换为std::remove_cv_t<std::remove_reference_t<decltype(*std::begin(container))>>实现相同效果。
内容的提问来源于stack exchange,提问作者Drake Johnson
相关产品推荐
相关产品推荐

