I am trying to implement a similar function as fmt::join
with the difference that it needs to be used in conjunction with std::format
I have sofar the following example (link):
#include <ranges>
#include <vector>
#include <format>
#include <functional>
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);}));
}
I am stuck at the following compiler error:
<source>:15:16: error: no matching function for call to 'begin'
15 | return std::begin(m_range);
which I am struggling to understand since m_range
seems to satisfy the std::ranges::range
concept. Any idea what is wrong with the code and how it can be corrected
Sure, RANGE
satisfies std::ranges::range
, but const RANGE&
(which is what you have in a member function) doesn't.
The quickest-and-dirtiest fix (making RANGE m_range;
mutable
) makes this compile but gives incorrect output: https://godbolt.org/z/sxPbMGzsq (and probably isn't advisable because of const-correctness)
Also you should call std::ranges::begin
/end
instead of std::begin
/end
.
You eventually want to work towards non-const begin()
/end()
and take JoinView<RANGE> value
by-value (since std::format_args
copies the JoinView
and calls std::formatter
with a const
JoinView
).