use named arg with static name in compile-time API

to get arg index by name at compile-time
This commit is contained in:
Alexey Ochapov 2021-04-18 13:59:24 +03:00 committed by Victor Zverovich
parent ce6e7d8620
commit ca821982ee
4 changed files with 115 additions and 39 deletions

View File

@ -472,6 +472,26 @@ constexpr const auto& get([[maybe_unused]] const T& first,
return get<N - 1>(rest...); return get<N - 1>(rest...);
} }
constexpr int invalid_arg_index = -1;
template <int N, typename T, typename... Args, typename Char>
constexpr int get_arg_index_by_name(basic_string_view<Char> name) {
if constexpr (detail::is_statically_named_arg<T>()) {
if (name == T::name) return N;
}
if constexpr (sizeof...(Args) == 0) {
return invalid_arg_index;
} else {
return get_arg_index_by_name<N + 1, Args...>(name);
}
}
template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) {
return get_arg_index_by_name<0, Args...>(name);
}
template <int N, typename> struct get_type_impl; template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> { template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
@ -512,6 +532,17 @@ template <typename Char> struct code_unit {
} }
}; };
// This ensures that the argument type is convertile to `const T&`.
template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) {
const auto& arg = get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value;
} else {
return arg;
}
}
template <typename Char> template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {}; struct is_compiled_format<code_unit<Char>> : std::true_type {};
@ -521,14 +552,8 @@ template <typename Char, typename T, int N> struct field {
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr OutputIt format(OutputIt out, const Args&... args) const {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) { const T& arg = get_arg_checked<T, N>(args...);
const auto& arg = get<N>(args...).value; return write<Char>(out, arg);
return write<Char>(out, arg);
} else {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
return write<Char>(out, arg);
}
} }
}; };
@ -574,8 +599,7 @@ template <typename Char, typename T, int N> struct spec_field {
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`. const T& arg = get_arg_checked<T, N>(args...);
const T& arg = get<N>(args...);
const auto& vargs = const auto& vargs =
make_format_args<basic_format_context<OutputIt, Char>>(args...); make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs); basic_format_context<OutputIt, Char> ctx(out, vargs);
@ -687,6 +711,35 @@ constexpr auto parse_arg_id(const Char* begin, const Char* end) {
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end}; return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
} }
template <typename T, typename Enable = void> struct field_type {
using type = remove_cvref_t<T>;
};
template <typename T>
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
using type = remove_cvref_t<decltype(T::value)>;
};
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
typename S>
constexpr auto parse_replacement_field_then_tail(S format_str) {
using char_type = typename S::char_type;
constexpr basic_string_view<char_type> str = format_str;
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
if constexpr (c == '}') {
return parse_tail<Args, END_POS + 1, NEXT_ID>(
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
format_str);
} else if constexpr (c == ':') {
constexpr auto result = parse_specs<typename field_type<T>::type>(
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
return parse_tail<Args, result.end, result.next_arg_id>(
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
result.fmt},
format_str);
}
}
// Compiles a non-empty format string and returns the compiled representation // Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input. // or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S> template <typename Args, size_t POS, int ID, typename S>
@ -701,17 +754,11 @@ constexpr auto compile_format_string(S format_str) {
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
static_assert(ID != manual_indexing_id, static_assert(ID != manual_indexing_id,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
using id_type = get_type<ID, Args>; constexpr auto next_id =
if constexpr (str[POS + 1] == '}') { ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
constexpr auto next_id = return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
ID != manual_indexing_id ? ID + 1 : manual_indexing_id; POS + 1, ID, next_id>(
return parse_tail<Args, POS + 2, next_id>( format_str);
field<char_type, id_type, ID>(), format_str);
} else {
constexpr auto result = parse_specs<id_type>(str, POS + 2, ID + 1);
return parse_tail<Args, result.end, result.next_arg_id>(
spec_field<char_type, id_type, ID>{result.fmt}, format_str);
}
} else { } else {
constexpr auto arg_id_result = constexpr auto arg_id_result =
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size()); parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
@ -724,24 +771,27 @@ constexpr auto compile_format_string(S format_str) {
ID == manual_indexing_id || ID == 0, ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.val.index; constexpr auto arg_index = arg_id_result.arg_id.val.index;
using id_type = get_type<arg_index, Args>; return parse_replacement_field_then_tail<get_type<arg_index, Args>,
if constexpr (c == '}') { Args, arg_id_end_pos,
return parse_tail<Args, arg_id_end_pos + 1, manual_indexing_id>( arg_index, manual_indexing_id>(
field<char_type, id_type, arg_index>(), format_str); format_str);
} else if constexpr (c == ':') {
constexpr auto result =
parse_specs<id_type>(str, arg_id_end_pos + 1, 0);
return parse_tail<Args, result.end, result.next_arg_id>(
spec_field<char_type, id_type, arg_index>{result.fmt},
format_str);
}
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
if constexpr (c == '}') { constexpr auto arg_index =
return parse_tail<Args, arg_id_end_pos + 1, ID>( get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
runtime_named_field<char_type>{arg_id_result.arg_id.val.name}, if constexpr (arg_index != invalid_arg_index) {
format_str); constexpr auto next_id =
} else if constexpr (c == ':') { ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return unknown_format(); // no type info for specs parsing return parse_replacement_field_then_tail<
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
arg_index, next_id>(format_str);
} else {
if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
format_str);
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}
} }
} }
} }

View File

@ -451,7 +451,8 @@ template <typename Char> class basic_string_view {
return result; return result;
} }
friend bool operator==(basic_string_view lhs, basic_string_view rhs) { FMT_CONSTEXPR_CHAR_TRAITS friend bool operator==(basic_string_view lhs,
basic_string_view rhs) {
return lhs.compare(rhs) == 0; return lhs.compare(rhs) == 0;
} }
friend bool operator!=(basic_string_view lhs, basic_string_view rhs) { friend bool operator!=(basic_string_view lhs, basic_string_view rhs) {

View File

@ -3941,6 +3941,9 @@ template <typename Char> struct udl_formatter {
} }
}; };
template <typename T, typename = void>
struct is_statically_named_arg : std::false_type {};
# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS # if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
template <typename T, typename Char, size_t N, fixed_string<Char, N> Str> template <typename T, typename Char, size_t N, fixed_string<Char, N> Str>
struct statically_named_arg : view { struct statically_named_arg : view {
@ -3953,6 +3956,10 @@ struct statically_named_arg : view {
template <typename T, typename Char, size_t N, fixed_string<Char, N> Str> template <typename T, typename Char, size_t N, fixed_string<Char, N> Str>
struct is_named_arg<statically_named_arg<T, Char, N, Str>> : std::true_type {}; struct is_named_arg<statically_named_arg<T, Char, N, Str>> : std::true_type {};
template <typename T, typename Char, size_t N, fixed_string<Char, N> Str>
struct is_statically_named_arg<statically_named_arg<T, Char, N, Str>>
: std::true_type {};
template <typename Char, size_t N, fixed_string<Char, N> Str> struct udl_arg { template <typename Char, size_t N, fixed_string<Char, N> Str> struct udl_arg {
template <typename T> auto operator=(T&& value) const { template <typename T> auto operator=(T&& value) const {
return statically_named_arg<T, Char, N, Str>(std::forward<T>(value)); return statically_named_arg<T, Char, N, Str>(std::forward<T>(value));

View File

@ -210,6 +210,11 @@ TEST(CompileTest, ManualOrdering) {
} }
TEST(CompileTest, Named) { TEST(CompileTest, Named) {
auto runtime_named_field_compiled =
fmt::detail::compile<decltype(fmt::arg("arg", 42))>(FMT_COMPILE("{arg}"));
static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
fmt::detail::runtime_named_field<char>>);
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42))); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41), EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
fmt::arg("arg", 43))); fmt::arg("arg", 43)));
@ -237,6 +242,19 @@ TEST(CompileTest, Named) {
EXPECT_THROW(fmt::format(FMT_COMPILE("{invalid}"), fmt::arg("valid", 42)), EXPECT_THROW(fmt::format(FMT_COMPILE("{invalid}"), fmt::arg("valid", 42)),
fmt::format_error); fmt::format_error);
# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
using namespace fmt::literals;
auto statically_named_field_compiled =
fmt::detail::compile<decltype("arg"_a = 42)>(FMT_COMPILE("{arg}"));
static_assert(std::is_same_v<decltype(statically_named_field_compiled),
fmt::detail::field<char, int, 0>>);
EXPECT_EQ("41 43",
fmt::format(FMT_COMPILE("{a0} {a1}"), "a0"_a = 41, "a1"_a = 43));
EXPECT_EQ("41 43",
fmt::format(FMT_COMPILE("{a1} {a0}"), "a0"_a = 43, "a1"_a = 41));
# endif
} }
TEST(CompileTest, FormatTo) { TEST(CompileTest, FormatTo) {