diff --git a/include/fmt/compile.h b/include/fmt/compile.h index b83f3167..94a30b10 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -472,6 +472,26 @@ constexpr const auto& get([[maybe_unused]] const T& first, return get(rest...); } +constexpr int invalid_arg_index = -1; + +template +constexpr int get_arg_index_by_name(basic_string_view name) { + if constexpr (detail::is_statically_named_arg()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) == 0) { + return invalid_arg_index; + } else { + return get_arg_index_by_name(name); + } +} + +template +constexpr int get_arg_index_by_name(basic_string_view name, + type_list) { + return get_arg_index_by_name<0, Args...>(name); +} + template struct get_type_impl; template struct get_type_impl> { @@ -512,6 +532,17 @@ template struct code_unit { } }; +// This ensures that the argument type is convertile to `const T&`. +template +constexpr const T& get_arg_checked(const Args&... args) { + const auto& arg = get(args...); + if constexpr (detail::is_named_arg>()) { + return arg.value; + } else { + return arg; + } +} + template struct is_compiled_format> : std::true_type {}; @@ -521,14 +552,8 @@ template struct field { template constexpr OutputIt format(OutputIt out, const Args&... args) const { - if constexpr (is_named_arg::type>::value) { - const auto& arg = get(args...).value; - return write(out, arg); - } else { - // This ensures that the argument type is convertile to `const T&`. - const T& arg = get(args...); - return write(out, arg); - } + const T& arg = get_arg_checked(args...); + return write(out, arg); } }; @@ -574,8 +599,7 @@ template struct spec_field { template constexpr OutputIt format(OutputIt out, const Args&... args) const { - // This ensures that the argument type is convertile to `const T&`. - const T& arg = get(args...); + const T& arg = get_arg_checked(args...); const auto& vargs = make_format_args>(args...); basic_format_context ctx(out, vargs); @@ -687,6 +711,35 @@ constexpr auto parse_arg_id(const Char* begin, const Char* end) { return parse_arg_id_result{handler.arg_id, arg_id_end}; } +template struct field_type { + using type = remove_cvref_t; +}; + +template +struct field_type::value>> { + using type = remove_cvref_t; +}; + +template +constexpr auto parse_replacement_field_then_tail(S format_str) { + using char_type = typename S::char_type; + constexpr basic_string_view str = format_str; + constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); + if constexpr (c == '}') { + return parse_tail( + field::type, ARG_INDEX>(), + format_str); + } else if constexpr (c == ':') { + constexpr auto result = parse_specs::type>( + str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); + return parse_tail( + spec_field::type, ARG_INDEX>{ + result.fmt}, + format_str); + } +} + // Compiles a non-empty format string and returns the compiled representation // or unknown_format() on unrecognized input. template @@ -701,17 +754,11 @@ constexpr auto compile_format_string(S format_str) { } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { static_assert(ID != manual_indexing_id, "cannot switch from manual to automatic argument indexing"); - using id_type = get_type; - if constexpr (str[POS + 1] == '}') { - constexpr auto next_id = - ID != manual_indexing_id ? ID + 1 : manual_indexing_id; - return parse_tail( - field(), format_str); - } else { - constexpr auto result = parse_specs(str, POS + 2, ID + 1); - return parse_tail( - spec_field{result.fmt}, format_str); - } + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail, Args, + POS + 1, ID, next_id>( + format_str); } else { constexpr auto arg_id_result = parse_arg_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, "cannot switch from automatic to manual argument indexing"); constexpr auto arg_index = arg_id_result.arg_id.val.index; - using id_type = get_type; - if constexpr (c == '}') { - return parse_tail( - field(), format_str); - } else if constexpr (c == ':') { - constexpr auto result = - parse_specs(str, arg_id_end_pos + 1, 0); - return parse_tail( - spec_field{result.fmt}, - format_str); - } + return parse_replacement_field_then_tail, + Args, arg_id_end_pos, + arg_index, manual_indexing_id>( + format_str); } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { - if constexpr (c == '}') { - return parse_tail( - runtime_named_field{arg_id_result.arg_id.val.name}, - format_str); - } else if constexpr (c == ':') { - return unknown_format(); // no type info for specs parsing + constexpr auto arg_index = + get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); + if constexpr (arg_index != invalid_arg_index) { + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail< + decltype(get_type::value), Args, arg_id_end_pos, + arg_index, next_id>(format_str); + } else { + if constexpr (c == '}') { + return parse_tail( + runtime_named_field{arg_id_result.arg_id.val.name}, + format_str); + } else if constexpr (c == ':') { + return unknown_format(); // no type info for specs parsing + } } } } diff --git a/include/fmt/core.h b/include/fmt/core.h index 62ab7867..3a85e2cd 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -451,7 +451,8 @@ template class basic_string_view { 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; } friend bool operator!=(basic_string_view lhs, basic_string_view rhs) { diff --git a/include/fmt/format.h b/include/fmt/format.h index 8b1e8cb6..e15131d6 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3941,6 +3941,9 @@ template struct udl_formatter { } }; +template +struct is_statically_named_arg : std::false_type {}; + # if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS template Str> struct statically_named_arg : view { @@ -3953,6 +3956,10 @@ struct statically_named_arg : view { template Str> struct is_named_arg> : std::true_type {}; +template Str> +struct is_statically_named_arg> + : std::true_type {}; + template Str> struct udl_arg { template auto operator=(T&& value) const { return statically_named_arg(std::forward(value)); diff --git a/test/compile-test.cc b/test/compile-test.cc index 7bae785d..56056ec4 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -210,6 +210,11 @@ TEST(CompileTest, ManualOrdering) { } TEST(CompileTest, Named) { + auto runtime_named_field_compiled = + fmt::detail::compile(FMT_COMPILE("{arg}")); + static_assert(std::is_same_v>); + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42))); EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41), fmt::arg("arg", 43))); @@ -237,6 +242,19 @@ TEST(CompileTest, Named) { EXPECT_THROW(fmt::format(FMT_COMPILE("{invalid}"), fmt::arg("valid", 42)), fmt::format_error); + +# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS + using namespace fmt::literals; + auto statically_named_field_compiled = + fmt::detail::compile(FMT_COMPILE("{arg}")); + static_assert(std::is_same_v>); + + 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) {