Skip to content

Commit

Permalink
add support for statically named arguments with FMT_STRING
Browse files Browse the repository at this point in the history
  • Loading branch information
alexezeder committed May 13, 2021
1 parent 8f0fadf commit bc9dfda
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 32 deletions.
16 changes: 1 addition & 15 deletions include/fmt/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,24 +194,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
83 changes: 80 additions & 3 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 @@ -2244,21 +2255,87 @@ class compile_parse_context
using base::check_arg_id;
};

template <typename T> constexpr bool check_named_argument() {
return !detail::is_named_arg<T>() ||
(detail::is_named_arg<T>() && detail::is_statically_named_arg<T>());
}

template <typename... Args, FMT_ENABLE_IF(sizeof...(Args) == 0)>
constexpr bool check_named_arguments() {
return true;
}

template <typename T, typename... Args> constexpr bool check_named_arguments() {
return check_named_argument<T>() && check_named_arguments<Args...>();
}

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:
explicit FMT_CONSTEXPR format_string_checker(
basic_string_view<Char> format_str, ErrorHandler eh)
: context_(format_str, num_args, eh),
parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {}
parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {
static_assert(
check_named_arguments<Args...>(),
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
"use statically named arguments with compile-time format strings: "
"fmt::arg(\"name\", value) -> \"name\"_a = value"
#else
"compile-time checks don't support named arguments, "
"please use C++20 or/and a newer compiler"
#endif
);
}

FMT_CONSTEXPR void on_text(const Char*, const Char*) {}

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 don't support named arguments, "
"please use C++20 or/and a newer compiler");
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 @@ -3408,9 +3398,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
16 changes: 15 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,14 @@ 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));
# else
EXPECT_ERROR("{foo}",
"compile-time checks don't support named arguments, "
"please use C++20 or/and a newer compiler",
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

0 comments on commit bc9dfda

Please sign in to comment.