实现适配std::format的join方法时遇编译错误求解决
问题:实现兼容std::format的join函数编译错误分析与修复
我想要实现一个类似fmt::join的函数,区别在于它需要与std::format配合使用。编写的示例代码如下:
#include <ranges> #include <vector> #include <format> #include <functional> #include <array> template<std::ranges::range RANGE> struct JoinView { RANGE m_range; std::string_view m_sep; using Iter = std::ranges::iterator_t<RANGE>; using Sentinel = std::ranges::sentinel_t<RANGE>; JoinView(RANGE&& range, std::string_view sep) : m_range(std::move(range)), m_sep(sep) {}; auto begin() const { return std::begin(m_range); } auto end() const { return std::end(m_range); } }; template<typename INPUT_IT, typename OUTPUT_IT> constexpr auto copy_str(INPUT_IT begin, INPUT_IT end, OUTPUT_IT out) -> OUTPUT_IT { while (begin != end) *out++ = static_cast<char>(*begin++); return out; } template<std::ranges::range RANGE> auto join_detail(RANGE&& range, std::string_view sep) -> JoinView<RANGE> { return { std::forward<RANGE>(range), sep }; } template<std::ranges::range RANGE, typename Projection = std::identity> inline auto join(RANGE&& range, std::string_view sep, Projection&& proj = std::identity{}) { auto joined = range | std::views::transform([&proj](const auto& element) { return std::invoke(proj, element); }) | std::views::join_with(sep); return join_detail(std::move(joined), sep); }; template <std::ranges::range RANGE> struct std::formatter<JoinView<RANGE>, char> { private: using ValueType = std::iter_value_t<typename JoinView<RANGE>::Iter>; formatter<std::remove_cvref_t<ValueType>, char> m_value_formatter; public: template <typename PARSE_CONTEXT> constexpr auto parse(PARSE_CONTEXT& ctx) -> const char* { return m_value_formatter.parse(ctx); } template <typename FORMAT_CONTEXT> auto format(const JoinView<RANGE>& value, FORMAT_CONTEXT& ctx) const -> decltype(ctx.out()) { auto it = value.begin(); auto out = ctx.out(); if (it != value.end()) { out = m_value_formatter.format(*it, ctx); ++it; while (it != value.end()) { out = copy_str(value.m_sep.begin(), value.m_sep.end(), out); ctx.advance_to(out); out = m_value_formatter.format(*it, ctx); ++it; } } return out; } }; int main() { auto dd = std::format("{}", join( std::array<int,3>{2,3,4}, ",", [](const auto& e){return std::format("{}", e);})); }
编译时遇到如下错误:
<source>:15:16: error: no matching function for call to 'begin' 15 | return std::begin(m_range);
我对此感到困惑,因为m_range似乎满足std::ranges::range概念,请问代码存在什么问题,该如何修正?
错误原因分析
JoinView的begin()和end()成员函数被声明为const,但std::views::join_with返回的视图属于非常量范围——这类视图的迭代器获取操作(begin()/end())不支持常量调用,因为视图内部可能维护迭代状态,无法在常量上下文中安全访问。- 代码存在冗余设计:先用
views::join_with完成了拼接,又将结果传给JoinView,完全浪费了join_with的功能,同时引入了不必要的视图嵌套,加剧了常量访问的冲突。
修复方案
- 移除
JoinView中begin()/end()的const限定,适配视图类型的非const迭代需求。 - 重构
join函数,直接将原序列、分隔符和投影逻辑封装到JoinView中,不再依赖views::join_with。 - 调整
std::formatter实现,直接对原序列元素应用投影后格式化,手动插入分隔符,确保逻辑清晰且符合std::format的要求。
修正后的完整代码
#include <ranges> #include <vector> #include <format> #include <functional> #include <array> template<std::ranges::range RANGE, typename Projection> struct JoinView { RANGE m_range; std::string_view m_sep; Projection m_proj; using Iter = std::ranges::iterator_t<RANGE>; using Sentinel = std::ranges::sentinel_t<RANGE>; JoinView(RANGE&& range, std::string_view sep, Projection&& proj) : m_range(std::move(range)), m_sep(sep), m_proj(std::forward<Projection>(proj)) {} auto begin() { return std::begin(m_range); } auto end() { return std::end(m_range); } }; template<std::ranges::range RANGE, typename Projection = std::identity> inline auto join(RANGE&& range, std::string_view sep, Projection&& proj = std::identity{}) { return JoinView<RANGE, std::decay_t<Projection>>( std::forward<RANGE>(range), sep, std::forward<Projection>(proj) ); } template<std::ranges::range RANGE, typename Projection> struct std::formatter<JoinView<RANGE, Projection>, char> { private: using ElementType = std::ranges::range_value_t<RANGE>; using ProjectedType = std::invoke_result_t<Projection, const ElementType&>; formatter<std::remove_cvref_t<ProjectedType>, char> m_value_formatter; public: template <typename PARSE_CONTEXT> constexpr auto parse(PARSE_CONTEXT& ctx) -> const char* { return m_value_formatter.parse(ctx); } template <typename FORMAT_CONTEXT> auto format(const JoinView<RANGE, Projection>& value, FORMAT_CONTEXT& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); auto it = value.begin(); const auto end_it = value.end(); if (it != end_it) { // 格式化第一个元素(应用投影) out = m_value_formatter.format(std::invoke(value.m_proj, *it), ctx); ++it; // 格式化剩余元素,先插分隔符再格式化 for (; it != end_it; ++it) { // 插入分隔符 out = std::copy(value.m_sep.begin(), value.m_sep.end(), out); ctx.advance_to(out); // 格式化当前元素 out = m_value_formatter.format(std::invoke(value.m_proj, *it), ctx); } } return out; } }; int main() { auto dd = std::format("{}", join(std::array<int,3>{2,3,4}, ",", [](const auto& e){return std::format("{}", e);})); // 输出结果:"2,3,4" }
内容的提问来源于stack exchange,提问作者ATK
相关产品推荐
相关产品推荐

