Skip to content
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

Add support for incomplete types #4383

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ add_fmt_test(assert-test)
add_fmt_test(chrono-test)
add_fmt_test(color-test)
add_fmt_test(gtest-extra-test)
add_fmt_test(incomplete-type-test)
add_fmt_test(format-test mock-allocator.h)
if (MSVC)
target_compile_options(format-test PRIVATE /bigobj)
Expand Down
41 changes: 41 additions & 0 deletions test/incomplete-type-test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.

#include "fmt/format.h"
#include "fmt/color.h"
#include "gtest/gtest.h"

// Only defined after all the tests.
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));
EXPECT_EQ(fmt::format("{:4}", fmt::styled(external_instance, fg(fmt::color::red))), fmt::format("{:4}", fmt::styled(42, fg(fmt::color::red))));
}

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);
}