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 statically named arguments with FMT_STRING #2281

Merged
merged 1 commit into from
May 14, 2021
Merged
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
16 changes: 1 addition & 15 deletions include/fmt/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,24 +191,10 @@ constexpr const auto& get([[maybe_unused]] const T& first,
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);
return get_arg_index_by_name<Args...>(name);
}

template <int N, typename> struct get_type_impl;
Expand Down
52 changes: 50 additions & 2 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down Expand Up @@ -991,6 +1001,7 @@ template <typename Char>
inline void init_named_args(named_arg_info<Char>*, int, int) {}

template <typename T> struct is_named_arg : std::false_type {};
template <typename T> struct is_statically_named_arg : std::false_type {};

template <typename T, typename Char>
struct is_named_arg<named_arg<Char, T>> : std::true_type {};
Expand Down Expand Up @@ -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 <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... Args, typename Char>
constexpr int get_arg_index_by_name(basic_string_view<Char> name) {
if constexpr (sizeof...(Args) == 0) {
return invalid_arg_index;
} else {
return get_arg_index_by_name<0, Args...>(name);
}
}
#else
template <typename... Args, typename Char>
constexpr int get_arg_index_by_name(basic_string_view<Char>) {
return invalid_arg_index;
}
#endif

template <typename Char, typename ErrorHandler, typename... Args>
class format_string_checker {
public:
Expand All @@ -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<Char>) {
on_error("compile-time checks don't support named arguments");
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char> id) {
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
auto index = get_arg_index_by_name<Args...>(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*) {}
Expand Down
13 changes: 0 additions & 13 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -3226,9 +3216,6 @@ 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
template <typename T, typename Char, size_t N, fixed_string<Char, N> Str>
struct statically_named_arg : view {
Expand Down
17 changes: 16 additions & 1 deletion test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down