Skip to content

Add support for incomplete types #4383

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions include/fmt/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ enum {
pointer_set = set(type::pointer_type)
};

struct view {};
template <typename T> struct is_view : std::false_type {};

template <typename Char, typename T> struct named_arg;
template <typename T> struct is_named_arg : std::false_type {};
Expand All @@ -1039,14 +1039,17 @@ template <typename T> struct is_static_named_arg : std::false_type {};
template <typename Char, typename T>
struct is_named_arg<named_arg<Char, T>> : std::true_type {};

template <typename Char, typename T> struct named_arg : view {
template <typename Char, typename T> struct named_arg {
const Char* name;
const T& value;

named_arg(const Char* n, const T& v) : name(n), value(v) {}
static_assert(!is_named_arg<T>::value, "nested named arguments");
};

template <typename Char, typename T>
struct is_view<named_arg<Char, T>> : std::true_type {};

template <bool B = false> constexpr auto count() -> int { return B ? 1 : 0; }
template <bool B1, bool B2, bool... Tail> constexpr auto count() -> int {
return (B1 ? 1 : 0) + count<B2, Tail...>();
Expand Down Expand Up @@ -2715,7 +2718,7 @@ template <typename... T> struct fstring {
template <size_t N>
FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) {
using namespace detail;
static_assert(count<(std::is_base_of<view, remove_reference_t<T>>::value &&
static_assert(count<(is_view<remove_cvref_t<T>>::value &&
std::is_reference<T>::value)...>() == 0,
"passing views as lvalues is disallowed");
if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
Expand Down
4 changes: 3 additions & 1 deletion include/fmt/color.h
Original file line number Diff line number Diff line change
Expand Up @@ -468,12 +468,14 @@ template <typename Char> inline void reset_color(buffer<Char>& buffer) {
buffer.append(reset_color.begin(), reset_color.end());
}

template <typename T> struct styled_arg : view {
template <typename T> struct styled_arg {
const T& value;
text_style style;
styled_arg(const T& v, text_style s) : value(v), style(s) {}
};

template <typename T> struct is_view<styled_arg<T>> : std::true_type {};

template <typename Char>
void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args) {
Expand Down
6 changes: 5 additions & 1 deletion include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -3554,13 +3554,17 @@ FMT_CONSTEXPR void handle_dynamic_spec(
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <typename T, typename Char, size_t N,
fmt::detail::fixed_string<Char, N> Str>
struct static_named_arg : view {
struct static_named_arg {
static constexpr auto name = Str.data;

const T& value;
static_named_arg(const T& v) : value(v) {}
};

template <typename T, typename Char, size_t N,
fmt::detail::fixed_string<Char, N> Str>
struct is_view<static_named_arg<T, Char, N, Str>> : std::true_type {};

template <typename T, typename Char, size_t N,
fmt::detail::fixed_string<Char, N> Str>
struct is_named_arg<static_named_arg<T, Char, N, Str>> : std::true_type {};
Expand Down
18 changes: 16 additions & 2 deletions include/fmt/ranges.h
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ struct formatter<
};

template <typename It, typename Sentinel, typename Char = char>
struct join_view : detail::view {
struct join_view {
It begin;
Sentinel end;
basic_string_view<Char> sep;
Expand All @@ -629,6 +629,13 @@ struct join_view : detail::view {
: begin(std::move(b)), end(e), sep(s) {}
};

namespace detail {

template <typename It, typename Sentinel, typename Char>
struct is_view<join_view<It, Sentinel, Char>> : std::true_type {};

} // namespace detail

template <typename It, typename Sentinel, typename Char>
struct formatter<join_view<It, Sentinel, Char>, Char> {
private:
Expand Down Expand Up @@ -670,14 +677,21 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
}
};

template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
template <typename Char, typename Tuple> struct tuple_join_view {
const Tuple& tuple;
basic_string_view<Char> sep;

tuple_join_view(const Tuple& t, basic_string_view<Char> s)
: tuple(t), sep{s} {}
};

namespace detail {

template <typename Char, typename Tuple>
struct is_view<tuple_join_view<Char, Tuple>> : std::true_type {};

} // namespace detail

// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
Expand Down
30 changes: 30 additions & 0 deletions test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2586,3 +2586,33 @@ TEST(base_test, format_byte) {
EXPECT_EQ(s, "42");
}
#endif

// Only defined after the test case.
struct incomplete_type;
extern const incomplete_type& external_instance;

FMT_BEGIN_NAMESPACE

template <> struct formatter<incomplete_type> : formatter<int> {
auto format(const incomplete_type& x, context& ctx) const
-> decltype(ctx.out());
};

FMT_END_NAMESPACE

TEST(incomplete_type_test, format) {
EXPECT_EQ(fmt::format("{}", external_instance), fmt::format("{}", 42));
EXPECT_EQ(fmt::format("{:4}", external_instance), fmt::format("{:4}", 42));
}

struct incomplete_type {
int i;
};

const incomplete_type& external_instance{42};

auto fmt::formatter<incomplete_type>::format(const incomplete_type& x,
fmt::context& ctx) const
-> decltype(ctx.out()) {
return fmt::formatter<int>::format(x.i, ctx);
}