From 3533f982e1aed98e893806fbfe934d3d3e943c8f Mon Sep 17 00:00:00 2001 From: Alexey Ochapov Date: Thu, 13 May 2021 03:57:07 +0300 Subject: [PATCH] add support for statically named arguments with FMT_STRING --- include/fmt/compile.h | 16 +------------ include/fmt/core.h | 52 +++++++++++++++++++++++++++++++++++++++++-- include/fmt/format.h | 13 ----------- test/format-test.cc | 17 +++++++++++++- 4 files changed, 67 insertions(+), 31 deletions(-) diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 3160588bf793..128004bc5a7c 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -191,24 +191,10 @@ 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); + return get_arg_index_by_name(name); } template struct get_type_impl; diff --git a/include/fmt/core.h b/include/fmt/core.h index 24dbc7f5f4e1..4b5236f62230 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -281,6 +281,16 @@ # define FMT_COMPILE_TIME_CHECKS 0 #endif +#ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +# if defined(__cpp_nontype_template_args) && \ + ((FMT_GCC_VERSION >= 903 && __cplusplus >= 201709L) || \ + __cpp_nontype_template_args >= 201911L) +# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 1 +# else +# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0 +# endif +#endif + // Enable minimal optimizations for more compact code in debug mode. FMT_GCC_PRAGMA("GCC push_options") #ifndef __OPTIMIZE__ @@ -991,6 +1001,7 @@ template inline void init_named_args(named_arg_info*, int, int) {} template struct is_named_arg : std::false_type {}; +template struct is_statically_named_arg : std::false_type {}; template struct is_named_arg> : std::true_type {}; @@ -2425,6 +2436,36 @@ class compile_parse_context using base::check_arg_id; }; +constexpr int invalid_arg_index = -1; + +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS +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) { + if constexpr (sizeof...(Args) == 0) { + return invalid_arg_index; + } else { + return get_arg_index_by_name<0, Args...>(name); + } +} +#else +template +constexpr int get_arg_index_by_name(basic_string_view) { + return invalid_arg_index; +} +#endif + template class format_string_checker { public: @@ -2437,9 +2478,16 @@ class format_string_checker { FMT_CONSTEXPR int on_arg_id() { return context_.next_arg_id(); } FMT_CONSTEXPR int on_arg_id(int id) { return context_.check_arg_id(id), id; } - FMT_CONSTEXPR int on_arg_id(basic_string_view) { - on_error("compile-time checks don't support named arguments"); + FMT_CONSTEXPR int on_arg_id(basic_string_view id) { +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS + auto index = get_arg_index_by_name(id); + if (index == invalid_arg_index) on_error("named argument is not found"); + return context_.check_arg_id(index), index; +#else + (void)id; + on_error("compile-time checks for named arguments require C++20 support"); return 0; +#endif } FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} diff --git a/include/fmt/format.h b/include/fmt/format.h index cfed721e6186..722e4e13ba44 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -255,16 +255,6 @@ inline int ctzll(uint64_t x) { FMT_END_NAMESPACE #endif -#ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS -# if defined(__cpp_nontype_template_args) && \ - ((FMT_GCC_VERSION >= 903 && __cplusplus >= 201709L) || \ - __cpp_nontype_template_args >= 201911L) -# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 1 -# else -# define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0 -# endif -#endif - FMT_BEGIN_NAMESPACE namespace detail { @@ -3226,9 +3216,6 @@ 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 { diff --git a/test/format-test.cc b/test/format-test.cc index 50e96ef53b3e..cbb2f96b9a22 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1694,6 +1694,14 @@ TEST(format_test, compile_time_string) { EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42)); EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like())); +#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS + using namespace fmt::literals; + EXPECT_EQ("foobar", fmt::format(FMT_STRING("{foo}{bar}"), "bar"_a = "bar", + "foo"_a = "foo")); + EXPECT_EQ("", fmt::format(FMT_STRING(""))); + EXPECT_EQ("", fmt::format(FMT_STRING(""), "arg"_a = 42)); +#endif + (void)static_with_null; (void)static_with_null_wide; (void)static_no_null; @@ -2339,8 +2347,15 @@ TEST(format_test, format_string_errors) { # else fmt::print("warning: constexpr is broken in this version of MSVC\n"); # endif - EXPECT_ERROR("{foo", "compile-time checks don't support named arguments", +# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS + EXPECT_ERROR("{foo}", "named argument is not found", decltype("bar"_a = 42)); + EXPECT_ERROR("{foo}", "named argument is not found", + decltype(fmt::arg("foo", 42))); +# else + EXPECT_ERROR("{foo}", + "compile-time checks for named arguments require C++20 support", int); +# endif EXPECT_ERROR_NOARGS("{10000000000}", "number is too big"); EXPECT_ERROR_NOARGS("{0x}", "invalid format string"); EXPECT_ERROR_NOARGS("{-}", "invalid format string");