If you can think of a better title for this question then please suggest/update, because I am at my wits’ end.
I am trying to get a working formatter for my custom string class, and I have two solutions, but it seems whether or not they compile changes between just about every version of libfmt, with neither of them compiling on some releases.
Version A: https://godbolt.org/z/z1sPKETWd
#include <array>
#include <fmt/core.h>
template <std::size_t N>
class fixed_string {
public:
static_assert(N > 0);
constexpr fixed_string() : m_size(0) {};
constexpr fixed_string(const char* str) {
m_size = std::min(strlen(str), N - 1);
std::copy(str, str + m_size, &m_data[0]);
m_data[m_size] = 0;
}
constexpr auto begin() const noexcept { return &m_data[0]; }
constexpr auto end() const noexcept { return &m_data[m_size]; }
constexpr const char& operator[](std::size_t pos) const { return m_data[pos]; }
constexpr const char* data() const noexcept { return &m_data[0]; }
constexpr std::size_t size() const noexcept { return m_size; }
private:
std::size_t m_size;
std::array<char, N> m_data;
};
template <size_t N>
struct fmt::formatter<fixed_string<N>> : public fmt::formatter<std::string_view> {};
int main() {
fixed_string<40> str = "hello";
fmt::print(">>>{:^10}<<<", str);
}
- 8.1.1: COMPILES
- 9.0.0: FAIL
- 9.1.0: FAIL
- 10.0.0: FAIL
- 10.1.1: FAIL
- 10.2.1: FAIL
- 10.2.2 (trunk): FAIL
Version B: https://godbolt.org/z/7KPdj6WG3
#include <array>
#include <fmt/core.h>
template <std::size_t N>
class fixed_string {
public:
static_assert(N > 0);
constexpr fixed_string() : m_size(0) {};
constexpr fixed_string(const char* str) {
m_size = std::min(strlen(str), N - 1);
std::copy(str, str + m_size, &m_data[0]);
m_data[m_size] = 0;
}
constexpr auto begin() const noexcept { return &m_data[0]; }
constexpr auto end() const noexcept { return &m_data[m_size]; }
constexpr const char& operator[](std::size_t pos) const { return m_data[pos]; }
constexpr const char* data() const noexcept { return &m_data[0]; }
constexpr std::size_t size() const noexcept { return m_size; }
private:
std::size_t m_size;
std::array<char, N> m_data;
};
template <size_t N>
struct fmt::formatter<fixed_string<N>> : public fmt::formatter<std::string_view> {
constexpr auto format(const fixed_string<N>& str, auto& ctx) const {
return formatter<std::string_view>::format({ str.begin(), str.size() }, ctx);
}
};
int main() {
fixed_string<40> str = "hello";
fmt::print(">>>{:^10}<<<", str);
}
- 8.1.1: FAIL
- 9.0.0: COMPILES
- 9.1.0: COMPILES
- 10.0.0: COMPILES
- 10.1.1: FAIL
- 10.2.1: FAIL
- 10.2.2 (trunk): COMPILES
I have no idea what’s going on with libfmt and whether or not there’s a way to accomplish this that is going to compile on all of these releases.
If you want an example of compiler errors, here is Version B with libfmt 10.2.1.
<source>: In member function 'constexpr auto fmt::v10::formatter<fixed_string<N> >::format(const fixed_string<N>&, auto:16&) const':
<source>:27:49: error: 'format' is not a member of 'fmt::v10::formatter<std::basic_string_view<char> >'
27 | return formatter<std::string_view>::format({ str.begin(), str.size() }, ctx);
| ^~~~~~
In file included from <source>:2:
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h: In instantiation of 'constexpr decltype (ctx.begin()) fmt::v10::detail::parse_format_specs(ParseContext&) [with T = fixed_string<40>; ParseContext = compile_parse_context<char>; decltype (ctx.begin()) = const char*]':
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:2684:51: required from here
<source>:34:19: in 'constexpr' expansion of 'fmt::v10::basic_format_string<char, fixed_string<40>&>(">>>{:^10}<<<")'
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:2787:40: in 'constexpr' expansion of 'fmt::v10::detail::parse_format_string<true, char, format_string_checker<char, fixed_string<40> > >(((fmt::v10::basic_format_string<char, fixed_string<40>&>*)this)->fmt::v10::basic_format_string<char, fixed_string<40>&>::str_, fmt::v10::detail::format_string_checker<char, fixed_string<40> >(fmt::v10::basic_string_view<char>(((const char*)s))))'
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:2534:44: in 'constexpr' expansion of 'fmt::v10::detail::parse_replacement_field<char, format_string_checker<char, fixed_string<40> >&>((p + -1), end, (* & handler))'
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:2512:38: in 'constexpr' expansion of '(& handler)->fmt::v10::detail::format_string_checker<char, fixed_string<40> >::on_format_specs(adapter.fmt::v10::detail::parse_replacement_field<char, format_string_checker<char, fixed_string<40> >&>(const char*, const char*, format_string_checker<char, fixed_string<40> >&)::id_adapter::arg_id, (begin + 1), end)'
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:2593:45: error: 'fmt::v10::detail::type_is_unformattable_for<fixed_string<40>, char> _' has incomplete type
2593 | type_is_unformattable_for<T, char_type> _;
| ^
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h: In function 'int main()':
<source>:34:19: in 'constexpr' expansion of 'fmt::v10::basic_format_string<char, fixed_string<40>&>(">>>{:^10}<<<")'
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:2787:40: in 'constexpr' expansion of 'fmt::v10::detail::parse_format_string<true, char, format_string_checker<char, fixed_string<40> > >(((fmt::v10::basic_format_string<char, fixed_string<40>&>*)this)->fmt::v10::basic_format_string<char, fixed_string<40>&>::str_, fmt::v10::detail::format_string_checker<char, fixed_string<40> >(fmt::v10::basic_string_view<char>(((const char*)s))))'
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:2534:44: in 'constexpr' expansion of 'fmt::v10::detail::parse_replacement_field<char, format_string_checker<char, fixed_string<40> >&>((p + -1), end, (* & handler))'
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:2514:32: error: 'constexpr void fmt::v10::detail::format_string_checker<Char, Args>::on_error(const char*) [with Char = char; Args = {fixed_string<40>}]' called in a constant expression
2514 | return handler.on_error("unknown format specifier"), end;
| ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:2687:22: note: 'constexpr void fmt::v10::detail::format_string_checker<Char, Args>::on_error(const char*) [with Char = char; Args = {fixed_string<40>}]' is not usable as a 'constexpr' function because:
2687 | FMT_CONSTEXPR void on_error(const char* message) {
| ^~~~~~~~
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:2688:23: error: call to non-'constexpr' function 'void fmt::v10::detail::throw_format_error(const char*)'
2688 | throw_format_error(message);
| ~~~~~~~~~~~~~~~~~~^~~~~~~~~
/opt/compiler-explorer/libs/fmt/10.2.1/include/fmt/core.h:648:27: note: 'void fmt::v10::detail::throw_format_error(const char*)' declared here
648 | FMT_NORETURN FMT_API void throw_format_error(const char* message);
| ^~~~~~~~~~~~~~~~~~
Compiler returned: 1