You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现适配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的功能,同时引入了不必要的视图嵌套,加剧了常量访问的冲突。

修复方案

  1. 移除JoinView中begin()/end()的const限定,适配视图类型的非const迭代需求。
  2. 重构join函数,直接将原序列、分隔符和投影逻辑封装到JoinView中,不再依赖views::join_with。
  3. 调整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 22:44:57