Cleanup argument construction

This commit is contained in:
Victor Zverovich 2023-05-11 18:37:33 -07:00
parent d7592ad8bf
commit ea49c91cd1
5 changed files with 131 additions and 159 deletions

View File

@ -1301,9 +1301,6 @@ template <typename Context> class value {
} }
}; };
template <typename Context, typename T>
FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg<Context>;
// To minimize the number of types we need to deal with, long is translated // To minimize the number of types we need to deal with, long is translated
// either to int or to long long depending on its size. // either to int or to long long depending on its size.
enum { long_short = sizeof(long) == sizeof(int) }; enum { long_short = sizeof(long) == sizeof(int) };
@ -1497,110 +1494,6 @@ class appender : public std::back_insert_iterator<detail::buffer<char>> {
auto operator++(int) noexcept -> appender { return *this; } auto operator++(int) noexcept -> appender { return *this; }
}; };
// A formatting argument. It is a trivially copyable/constructible type to
// allow storage in basic_memory_buffer.
template <typename Context> class basic_format_arg {
private:
detail::value<Context> value_;
detail::type type_;
template <typename ContextType, typename T>
friend FMT_CONSTEXPR auto detail::make_arg(T&& value)
-> basic_format_arg<ContextType>;
template <typename Visitor, typename Ctx>
friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis,
const basic_format_arg<Ctx>& arg)
-> decltype(vis(0));
friend class basic_format_args<Context>;
friend class dynamic_format_arg_store<Context>;
using char_type = typename Context::char_type;
template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
friend struct detail::arg_data;
basic_format_arg(const detail::named_arg_info<char_type>* args, size_t size)
: value_(args, size) {}
public:
class handle {
public:
explicit handle(detail::custom_value<Context> custom) : custom_(custom) {}
void format(typename Context::parse_context_type& parse_ctx,
Context& ctx) const {
custom_.format(custom_.value, parse_ctx, ctx);
}
private:
detail::custom_value<Context> custom_;
};
constexpr basic_format_arg() : type_(detail::type::none_type) {}
constexpr explicit operator bool() const noexcept {
return type_ != detail::type::none_type;
}
auto type() const -> detail::type { return type_; }
auto is_integral() const -> bool { return detail::is_integral_type(type_); }
auto is_arithmetic() const -> bool {
return detail::is_arithmetic_type(type_);
}
};
/**
\rst
Visits an argument dispatching to the appropriate visit method based on
the argument type. For example, if the argument type is ``double`` then
``vis(value)`` will be called with the value of type ``double``.
\endrst
*/
FMT_MODULE_EXPORT
template <typename Visitor, typename Context>
FMT_CONSTEXPR FMT_INLINE auto visit_format_arg(
Visitor&& vis, const basic_format_arg<Context>& arg) -> decltype(vis(0)) {
switch (arg.type_) {
case detail::type::none_type:
break;
case detail::type::int_type:
return vis(arg.value_.int_value);
case detail::type::uint_type:
return vis(arg.value_.uint_value);
case detail::type::long_long_type:
return vis(arg.value_.long_long_value);
case detail::type::ulong_long_type:
return vis(arg.value_.ulong_long_value);
case detail::type::int128_type:
return vis(detail::convert_for_visit(arg.value_.int128_value));
case detail::type::uint128_type:
return vis(detail::convert_for_visit(arg.value_.uint128_value));
case detail::type::bool_type:
return vis(arg.value_.bool_value);
case detail::type::char_type:
return vis(arg.value_.char_value);
case detail::type::float_type:
return vis(arg.value_.float_value);
case detail::type::double_type:
return vis(arg.value_.double_value);
case detail::type::long_double_type:
return vis(arg.value_.long_double_value);
case detail::type::cstring_type:
return vis(arg.value_.string.data);
case detail::type::string_type:
using sv = basic_string_view<typename Context::char_type>;
return vis(sv(arg.value_.string.data, arg.value_.string.size));
case detail::type::pointer_type:
return vis(arg.value_.pointer);
case detail::type::custom_type:
return vis(typename basic_format_arg<Context>::handle(arg.value_.custom));
}
return vis(monostate());
}
namespace detail { namespace detail {
template <typename Char, typename InputIt> template <typename Char, typename InputIt>
@ -1695,30 +1588,128 @@ FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value<Context> {
} }
template <typename Context, typename T> template <typename Context, typename T>
FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg<Context> { FMT_CONSTEXPR auto make_arg(T& val) -> basic_format_arg<Context> {
auto arg = basic_format_arg<Context>(); auto arg = basic_format_arg<Context>();
arg.type_ = mapped_type_constant<T, Context>::value; arg.type_ = mapped_type_constant<T, Context>::value;
arg.value_ = make_value<Context>(value); arg.value_ = make_value<Context>(val);
return arg; return arg;
} }
// The DEPRECATED type template parameter is there to avoid an ODR violation template <bool PACKED, typename Context, typename T, FMT_ENABLE_IF(PACKED)>
// when using a fallback formatter in one translation unit and an implicit FMT_CONSTEXPR FMT_INLINE auto make_arg(T& val) -> value<Context> {
// conversion in another (not recommended).
template <bool IS_PACKED, typename Context, type, typename T,
FMT_ENABLE_IF(IS_PACKED)>
FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value<Context> {
return make_value<Context>(val); return make_value<Context>(val);
} }
template <bool PACKED, typename Context, typename T, FMT_ENABLE_IF(!PACKED)>
template <bool IS_PACKED, typename Context, type, typename T, FMT_CONSTEXPR inline auto make_arg(T& val) -> basic_format_arg<Context> {
FMT_ENABLE_IF(!IS_PACKED)> return make_arg<Context>(val);
FMT_CONSTEXPR inline auto make_arg(T&& value) -> basic_format_arg<Context> {
return make_arg<Context>(value);
} }
} // namespace detail } // namespace detail
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
// A formatting argument. It is a trivially copyable/constructible type to
// allow storage in basic_memory_buffer.
template <typename Context> class basic_format_arg {
private:
detail::value<Context> value_;
detail::type type_;
template <typename ContextType, typename T>
friend FMT_CONSTEXPR auto detail::make_arg(T& value)
-> basic_format_arg<ContextType>;
template <typename Visitor, typename Ctx>
friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis,
const basic_format_arg<Ctx>& arg)
-> decltype(vis(0));
friend class basic_format_args<Context>;
friend class dynamic_format_arg_store<Context>;
using char_type = typename Context::char_type;
template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
friend struct detail::arg_data;
basic_format_arg(const detail::named_arg_info<char_type>* args, size_t size)
: value_(args, size) {}
public:
class handle {
public:
explicit handle(detail::custom_value<Context> custom) : custom_(custom) {}
void format(typename Context::parse_context_type& parse_ctx,
Context& ctx) const {
custom_.format(custom_.value, parse_ctx, ctx);
}
private:
detail::custom_value<Context> custom_;
};
constexpr basic_format_arg() : type_(detail::type::none_type) {}
constexpr explicit operator bool() const noexcept {
return type_ != detail::type::none_type;
}
auto type() const -> detail::type { return type_; }
auto is_integral() const -> bool { return detail::is_integral_type(type_); }
auto is_arithmetic() const -> bool {
return detail::is_arithmetic_type(type_);
}
};
/**
\rst
Visits an argument dispatching to the appropriate visit method based on
the argument type. For example, if the argument type is ``double`` then
``vis(value)`` will be called with the value of type ``double``.
\endrst
*/
FMT_MODULE_EXPORT
template <typename Visitor, typename Context>
FMT_CONSTEXPR FMT_INLINE auto visit_format_arg(
Visitor&& vis, const basic_format_arg<Context>& arg) -> decltype(vis(0)) {
switch (arg.type_) {
case detail::type::none_type:
break;
case detail::type::int_type:
return vis(arg.value_.int_value);
case detail::type::uint_type:
return vis(arg.value_.uint_value);
case detail::type::long_long_type:
return vis(arg.value_.long_long_value);
case detail::type::ulong_long_type:
return vis(arg.value_.ulong_long_value);
case detail::type::int128_type:
return vis(detail::convert_for_visit(arg.value_.int128_value));
case detail::type::uint128_type:
return vis(detail::convert_for_visit(arg.value_.uint128_value));
case detail::type::bool_type:
return vis(arg.value_.bool_value);
case detail::type::char_type:
return vis(arg.value_.char_value);
case detail::type::float_type:
return vis(arg.value_.float_value);
case detail::type::double_type:
return vis(arg.value_.double_value);
case detail::type::long_double_type:
return vis(arg.value_.long_double_value);
case detail::type::cstring_type:
return vis(arg.value_.string.data);
case detail::type::string_type:
using sv = basic_string_view<typename Context::char_type>;
return vis(sv(arg.value_.string.data, arg.value_.string.size));
case detail::type::pointer_type:
return vis(arg.value_.pointer);
case detail::type::custom_type:
return vis(typename basic_format_arg<Context>::handle(arg.value_.custom));
}
return vis(monostate());
}
// Formatting context. // Formatting context.
template <typename OutputIt, typename Char> class basic_format_context { template <typename OutputIt, typename Char> class basic_format_context {
private: private:
@ -1822,9 +1813,7 @@ class format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
basic_format_args<Context>(*this), basic_format_args<Context>(*this),
#endif #endif
data_{detail::make_arg< data_{detail::make_arg<is_packed, Context>(args)...} {
is_packed, Context,
detail::mapped_type_constant<Args, Context>::value>(args)...} {
detail::init_named_args(data_.named_args(), 0, 0, args...); detail::init_named_args(data_.named_args(), 0, 0, args...);
} }
}; };

View File

@ -132,22 +132,23 @@ template <typename T, typename Context> class arg_converter {
if (const_check(sizeof(target_type) <= sizeof(int))) { if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings. // Extra casts are used to silence warnings.
if (is_signed) { if (is_signed) {
arg_ = detail::make_arg<Context>( auto n = static_cast<int>(static_cast<target_type>(value));
static_cast<int>(static_cast<target_type>(value))); arg_ = detail::make_arg<Context>(n);
} else { } else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type; using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
arg_ = detail::make_arg<Context>( auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
static_cast<unsigned>(static_cast<unsigned_type>(value))); arg_ = detail::make_arg<Context>(n);
} }
} else { } else {
if (is_signed) { if (is_signed) {
// glibc's printf doesn't sign extend arguments of smaller types: // glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254" // std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB. // but we don't have to do the same because it's a UB.
arg_ = detail::make_arg<Context>(static_cast<long long>(value)); auto n = static_cast<long long>(value);
arg_ = detail::make_arg<Context>(n);
} else { } else {
arg_ = detail::make_arg<Context>( auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
static_cast<typename make_unsigned_or_bool<U>::type>(value)); arg_ = detail::make_arg<Context>(n);
} }
} }
} }
@ -175,8 +176,8 @@ template <typename Context> class char_converter {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) { void operator()(T value) {
arg_ = detail::make_arg<Context>( auto c = static_cast<typename Context::char_type>(value);
static_cast<typename Context::char_type>(value)); arg_ = detail::make_arg<Context>(c);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@ -476,9 +477,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto str = visit_format_arg(get_cstring<Char>(), arg); auto str = visit_format_arg(get_cstring<Char>(), arg);
auto str_end = str + specs.precision; auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char()); auto nul = std::find(str, str_end, Char());
arg = make_arg<basic_printf_context<iterator, Char>>( auto sv = basic_string_view<Char>(
basic_string_view<Char>( str, to_unsigned(nul != str_end ? nul - str : specs.precision));
str, to_unsigned(nul != str_end ? nul - str : specs.precision))); arg = make_arg<basic_printf_context<iterator, Char>>(sv);
} }
if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false; if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false;
if (specs.fill[0] == '0') { if (specs.fill[0] == '0') {

View File

@ -361,10 +361,11 @@ VISIT_TYPE(unsigned long, unsigned long long);
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \ testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
EXPECT_CALL(visitor, visit(expected)); \ EXPECT_CALL(visitor, visit(expected)); \
using iterator = std::back_insert_iterator<buffer<Char>>; \ using iterator = std::back_insert_iterator<buffer<Char>>; \
auto var = value; \
fmt::visit_format_arg( \ fmt::visit_format_arg( \
visitor, \ visitor, \
fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \ fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \
value)); \ var)); \
} }
#define CHECK_ARG_SIMPLE(value) \ #define CHECK_ARG_SIMPLE(value) \

View File

@ -1773,20 +1773,6 @@ TEST(format_test, variadic) {
EXPECT_EQ("abc1", fmt::format("{}c{}", "ab", 1)); EXPECT_EQ("abc1", fmt::format("{}c{}", "ab", 1));
} }
TEST(format_test, dynamic) {
using ctx = fmt::format_context;
auto args = std::vector<fmt::basic_format_arg<ctx>>();
args.emplace_back(fmt::detail::make_arg<ctx>(42));
args.emplace_back(fmt::detail::make_arg<ctx>("abc1"));
args.emplace_back(fmt::detail::make_arg<ctx>(1.5f));
std::string result = fmt::vformat(
"{} and {} and {}",
fmt::format_args(args.data(), static_cast<int>(args.size())));
EXPECT_EQ("42 and abc1 and 1.5", result);
}
TEST(format_test, bytes) { TEST(format_test, bytes) {
auto s = fmt::format("{:10}", fmt::bytes("ёжик")); auto s = fmt::format("{:10}", fmt::bytes("ёжик"));
EXPECT_EQ("ёжик ", s); EXPECT_EQ("ёжик ", s);
@ -2098,8 +2084,8 @@ TEST(format_test, format_to_n_output_iterator) {
TEST(format_test, vformat_to) { TEST(format_test, vformat_to) {
using context = fmt::format_context; using context = fmt::format_context;
fmt::basic_format_arg<context> arg = fmt::detail::make_arg<context>(42); int n = 42;
auto args = fmt::basic_format_args<context>(&arg, 1); auto args = fmt::make_format_args<context>(n);
auto s = std::string(); auto s = std::string();
fmt::vformat_to(std::back_inserter(s), "{}", args); fmt::vformat_to(std::back_inserter(s), "{}", args);
EXPECT_EQ("42", s); EXPECT_EQ("42", s);

View File

@ -146,14 +146,9 @@ TEST(xchar_test, format_to) {
} }
TEST(xchar_test, vformat_to) { TEST(xchar_test, vformat_to) {
using wcontext = fmt::wformat_context; auto args = fmt::make_wformat_args(42);
fmt::basic_format_arg<wcontext> warg = fmt::detail::make_arg<wcontext>(42);
auto wargs = fmt::basic_format_args<wcontext>(&warg, 1);
auto w = std::wstring(); auto w = std::wstring();
fmt::vformat_to(std::back_inserter(w), L"{}", wargs); fmt::vformat_to(std::back_inserter(w), L"{}", args);
EXPECT_EQ(L"42", w);
w.clear();
fmt::vformat_to(std::back_inserter(w), FMT_STRING(L"{}"), wargs);
EXPECT_EQ(L"42", w); EXPECT_EQ(L"42", w);
} }