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

Compile-time named arguments #2243

Merged
merged 4 commits into from
Apr 25, 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
143 changes: 87 additions & 56 deletions include/fmt/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,6 @@

#include "format.h"

#ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
# if defined(__cpp_nontype_template_parameter_class) && \
(!FMT_GCC_VERSION || FMT_GCC_VERSION >= 903)
# 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 @@ -126,14 +117,6 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string)

#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
template <typename Char, size_t N> struct fixed_string {
constexpr fixed_string(const Char (&str)[N]) {
copy_str<Char, const Char*, Char*>(static_cast<const Char*>(str), str + N,
data);
}
Char data[N]{};
};

template <typename Char, size_t N, fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
Expand Down Expand Up @@ -489,6 +472,26 @@ 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);
}

template <int N, typename> struct get_type_impl;

template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
Expand Down Expand Up @@ -529,6 +532,17 @@ template <typename Char> struct code_unit {
}
};

// This ensures that the argument type is convertible 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>
struct is_compiled_format<code_unit<Char>> : std::true_type {};

Expand All @@ -538,14 +552,7 @@ template <typename Char, typename T, int N> struct field {

template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
const auto& arg = get<N>(args...).value;
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);
}
return write<Char>(out, get_arg_checked<T, N>(args...));
}
};

Expand Down Expand Up @@ -591,12 +598,10 @@ template <typename Char, typename T, int N> struct spec_field {

template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
const auto& vargs =
make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(arg, ctx);
return fmt.format(get_arg_checked<T, N>(args...), ctx);
}
};

Expand Down Expand Up @@ -704,6 +709,35 @@ constexpr auto parse_arg_id(const Char* begin, const Char* 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
// or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S>
Expand All @@ -718,17 +752,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<ID, Args>;
if constexpr (str[POS + 1] == '}') {
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_tail<Args, POS + 2, next_id>(
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);
}
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
POS + 1, ID, next_id>(
format_str);
} else {
constexpr auto arg_id_result =
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
Expand All @@ -741,24 +769,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<arg_index, Args>;
if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, manual_indexing_id>(
field<char_type, id_type, arg_index>(), 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);
}
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
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<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
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<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
}
}
}
}
Expand Down
30 changes: 16 additions & 14 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,8 @@ template <typename Char> 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) {
Expand Down Expand Up @@ -975,16 +976,22 @@ struct arg_data<T, Char, NUM_ARGS, 0> {
template <typename Char>
inline void init_named_args(named_arg_info<Char>*, int, int) {}

template <typename Char, typename T, typename... Tail>
template <typename T> struct is_named_arg : std::false_type {};

template <typename T, typename Char>
struct is_named_arg<named_arg<Char, T>> : std::true_type {};

template <typename Char, typename T, typename... Tail,
FMT_ENABLE_IF(!is_named_arg<T>::value)>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is runtime named arg handling. Why do we need to care about compile-time ones here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because first, we need to provide a new entity for named arguments storing and adapt {fmt} for it. I mean before using this new entity in compile-time API.
This function took named_arg<Char, T>, which does not work for newly introduced statically_named_arg.

void init_named_args(named_arg_info<Char>* named_args, int arg_count,
int named_arg_count, const T&, const Tail&... args) {
init_named_args(named_args, arg_count + 1, named_arg_count, args...);
}

template <typename Char, typename T, typename... Tail>
template <typename Char, typename T, typename... Tail,
FMT_ENABLE_IF(is_named_arg<T>::value)>
void init_named_args(named_arg_info<Char>* named_args, int arg_count,
int named_arg_count, const named_arg<Char, T>& arg,
const Tail&... args) {
int named_arg_count, const T& arg, const Tail&... args) {
named_args[named_arg_count++] = {arg.name, arg_count};
init_named_args(named_args, arg_count + 1, named_arg_count, args...);
}
Expand All @@ -993,11 +1000,6 @@ template <typename... Args>
FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int,
const Args&...) {}

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

template <typename T, typename Char>
struct is_named_arg<named_arg<Char, T>> : std::true_type {};

template <bool B = false> constexpr size_t count() { return B ? 1 : 0; }
template <bool B1, bool B2, bool... Tail> constexpr size_t count() {
return (B1 ? 1 : 0) + count<B2, Tail...>();
Expand Down Expand Up @@ -1273,10 +1275,10 @@ template <typename Context> struct arg_mapper {
return val;
}

template <typename T>
FMT_CONSTEXPR FMT_INLINE auto map(const named_arg<char_type, T>& val)
-> decltype(std::declval<arg_mapper>().map(val.value)) {
return map(val.value);
template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg)
-> decltype(std::declval<arg_mapper>().map(named_arg.value)) {
return map(named_arg.value);
}

unformattable map(...) { return {}; }
Expand Down
57 changes: 57 additions & 0 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,15 @@ inline int ctzll(uint64_t x) {
FMT_END_NAMESPACE
#endif

#ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
# if defined(__cpp_nontype_template_parameter_class) && \
(!FMT_GCC_VERSION || FMT_GCC_VERSION >= 903)
# 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 @@ -3908,6 +3917,19 @@ void vprint(basic_string_view<Char> format_str, wformat_args args) {
}

FMT_MODULE_EXPORT_END

#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
namespace detail {
template <typename Char, size_t N> struct fixed_string {
constexpr fixed_string(const Char (&str)[N]) {
copy_str<Char, const Char*, Char*>(static_cast<const Char*>(str), str + N,
data);
}
Char data[N]{};
};
} // namespace detail
#endif

#if FMT_USE_USER_DEFINED_LITERALS
namespace detail {
template <typename Char> struct udl_formatter {
Expand All @@ -3919,13 +3941,39 @@ 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 {
static constexpr auto name = Str.data;

const T& value;
statically_named_arg(const T& v) : value(v) {}
};

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 {};

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 T> auto operator=(T&& value) const {
return statically_named_arg<T, Char, N, Str>(std::forward<T>(value));
}
};
# else
template <typename Char> struct udl_arg {
const Char* str;

template <typename T> named_arg<Char, T> operator=(T&& value) const {
return {str, std::forward<T>(value)};
}
};
# endif
} // namespace detail
FMT_MODULE_EXPORT_BEGIN

Expand Down Expand Up @@ -3959,12 +4007,21 @@ constexpr detail::udl_formatter<wchar_t> operator"" _format(const wchar_t* s,
fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23);
\endrst
*/
# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
template <detail::fixed_string Str>
constexpr detail::udl_arg<remove_cvref_t<decltype(Str.data[0])>,
sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
operator""_a() {
return {};
}
# else
constexpr detail::udl_arg<char> operator"" _a(const char* s, size_t) {
return {s};
}
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
return {s};
}
# endif
} // namespace literals

FMT_MODULE_EXPORT_END
Expand Down
Loading