Improve compile-time checks

This commit is contained in:
Victor Zverovich
2021-05-18 19:38:52 -07:00
parent 21d93bfd33
commit e9c1c415b8
8 changed files with 343 additions and 298 deletions

View File

@@ -1642,27 +1642,6 @@ constexpr format_arg_store<Context, Args...> make_format_args(
return {args...};
}
/**
\rst
Constructs a `~fmt::format_arg_store` object that contains references
to arguments and can be implicitly converted to `~fmt::format_args`.
If ``format_str`` is a compile-time string then `make_args_checked` checks
its validity at compile time.
\endrst
*/
template <typename... Args, typename S, typename Char = char_t<S>>
FMT_INLINE auto make_args_checked(const S& format_str,
const remove_reference_t<Args>&... args)
-> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
static_assert(
detail::count<(
std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
detail::check_format_string<Args...>(format_str);
return {args...};
}
/**
\rst
Returns a named argument to be used in a formatting function.
@@ -2668,49 +2647,9 @@ void check_format_string(S format_str) {
(void)invalid_format;
}
// Converts a compile-time string to basic_string_view.
template <typename Char, size_t N>
constexpr auto compile_string_to_view(const Char (&s)[N])
-> basic_string_view<Char> {
// Remove trailing NUL character if needed. Won't be present if this is used
// with a raw character array (i.e. not defined as a string).
return {s, N - (std::char_traits<Char>::to_int_type(s[N - 1]) == 0 ? 1 : 0)};
}
template <typename Char>
constexpr auto compile_string_to_view(std_string_view<Char> s)
-> basic_string_view<Char> {
return {s.data(), s.size()};
}
#define FMT_STRING_IMPL(s, base, explicit) \
[] { \
/* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
/* Use a macro-like name to avoid shadowing warnings. */ \
struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \
using char_type = fmt::remove_cvref_t<decltype(s[0])>; \
FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \
operator fmt::basic_string_view<char_type>() const { \
return fmt::detail::compile_string_to_view<char_type>(s); \
} \
}; \
return FMT_COMPILE_STRING(); \
}()
/**
\rst
Constructs a compile-time format string from a string literal *s*.
**Example**::
// A compile-time error because 'd' is an invalid specifier for strings.
std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
\endrst
*/
#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string, )
template <typename Char>
void vformat_to(
buffer<type_identity_t<Char>>& buf, basic_string_view<Char> format_str,
buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
detail::locale_ref loc = {});
@@ -2730,8 +2669,6 @@ struct formatter<T, Char,
detail::dynamic_format_specs<Char> specs_;
public:
FMT_CONSTEXPR formatter() = default;
// Parses format specifiers stopping either at the end of the range or at the
// terminating '}'.
template <typename ParseContext>
@@ -2802,33 +2739,37 @@ struct formatter<T, Char,
-> decltype(ctx.out());
};
template <typename Char> struct basic_runtime { basic_string_view<Char> str; };
template <typename Char, typename... Args> class basic_format_string {
private:
basic_string_view<Char> str_;
public:
#if FMT_COMPILE_TIME_CHECKS
template <size_t N>
consteval basic_format_string(const char (&s)[N]) : str_(s) {
if constexpr (detail::count_named_args<Args...>() == 0) {
using checker = detail::format_string_checker<char, detail::error_handler,
remove_cvref_t<Args>...>;
detail::parse_format_string<true>(string_view(s, N), checker(s, {}));
}
}
#endif
template <typename S,
FMT_ENABLE_IF(
std::is_convertible<const S&, basic_string_view<Char>>::value)>
basic_format_string(const S& s) : str_(s) {
#if FMT_COMPILE_TIME_CHECKS
consteval
#endif
basic_format_string(const S& s)
: str_(s) {
static_assert(
detail::count<
(std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
#if FMT_COMPILE_TIME_CHECKS
if constexpr (detail::count_named_args<Args...>() == 0) {
using checker = detail::format_string_checker<Char, detail::error_handler,
remove_cvref_t<Args>...>;
detail::parse_format_string<true>(str_, checker(s, {}));
}
#else
detail::check_format_string<Args...>(s);
#endif
}
basic_format_string(basic_runtime<Char> r) : str_(r.str) {}
FMT_INLINE operator basic_string_view<Char>() const { return str_; }
};
@@ -2836,9 +2777,16 @@ template <typename Char, typename... Args> class basic_format_string {
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
template <typename... Args> using format_string = string_view;
template <typename S> auto runtime(const S& s) -> basic_string_view<char_t<S>> {
return s;
}
#else
template <typename... Args>
using format_string = basic_format_string<char, type_identity_t<Args>...>;
// Creates a runtime format string.
template <typename S> auto runtime(const S& s) -> basic_runtime<char_t<S>> {
return {{s}};
}
#endif
FMT_API auto vformat(string_view fmt, format_args args) -> std::string;

View File

@@ -2143,6 +2143,46 @@ FMT_CONSTEXPR void handle_dynamic_spec(int& value,
}
}
// Converts a compile-time string to basic_string_view.
template <typename Char, size_t N>
constexpr auto compile_string_to_view(const Char (&s)[N])
-> basic_string_view<Char> {
// Remove trailing NUL character if needed. Won't be present if this is used
// with a raw character array (i.e. not defined as a string).
return {s, N - (std::char_traits<Char>::to_int_type(s[N - 1]) == 0 ? 1 : 0)};
}
template <typename Char>
constexpr auto compile_string_to_view(std_string_view<Char> s)
-> basic_string_view<Char> {
return {s.data(), s.size()};
}
#define FMT_STRING_IMPL(s, base, explicit) \
[] { \
/* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
/* Use a macro-like name to avoid shadowing warnings. */ \
struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \
using char_type = fmt::remove_cvref_t<decltype(s[0])>; \
FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \
operator fmt::basic_string_view<char_type>() const { \
return fmt::detail::compile_string_to_view<char_type>(s); \
} \
}; \
return FMT_COMPILE_STRING(); \
}()
/**
\rst
Constructs a compile-time format string from a string literal *s*.
**Example**::
// A compile-time error because 'd' is an invalid specifier for strings.
std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
\endrst
*/
#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string, )
using format_func = void (*)(detail::buffer<char>&, int, const char*);
FMT_API void format_error_code(buffer<char>& out, int error_code,
@@ -2573,22 +2613,20 @@ FMT_MODULE_EXPORT_END
template <typename Char>
void detail::vformat_to(
detail::buffer<type_identity_t<Char>>& buf,
basic_string_view<Char> format_str,
detail::buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<buffer_context<type_identity_t<Char>>> args,
detail::locale_ref loc) {
using iterator = typename buffer_context<Char>::iterator;
auto out = buffer_appender<Char>(buf);
if (format_str.size() == 2 && equal2(format_str.data(), "{}")) {
if (fmt.size() == 2 && equal2(fmt.data(), "{}")) {
auto arg = args.get(0);
if (!arg) error_handler().on_error("argument not found");
visit_format_arg(default_arg_formatter<iterator, Char>{out, args, loc},
arg);
return;
}
format_handler<iterator, Char, buffer_context<Char>> h(out, format_str, args,
loc);
parse_format_string<false>(format_str, h);
format_handler<iterator, Char, buffer_context<Char>> h(out, fmt, args, loc);
parse_format_string<false>(fmt, h);
}
#ifndef FMT_HEADER_ONLY
@@ -2630,6 +2668,27 @@ template <typename OutputIt, typename Char = char>
using format_args_t FMT_DEPRECATED_ALIAS =
basic_format_args<basic_format_context<OutputIt, Char>>;
/**
\rst
Constructs a `~fmt::format_arg_store` object that contains references
to arguments and can be implicitly converted to `~fmt::format_args`.
If ``fmt`` is a compile-time string then `make_args_checked` checks
its validity at compile time.
\endrst
*/
template <typename... Args, typename S, typename Char = char_t<S>>
FMT_INLINE auto make_args_checked(const S& fmt,
const remove_reference_t<Args>&... args)
-> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
static_assert(
detail::count<(
std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
detail::check_format_string<Args...>(fmt);
return {args...};
}
template <typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_string<S>::value)>
inline void vformat_to(
@@ -2736,9 +2795,9 @@ namespace detail {
template <typename Char> struct udl_formatter {
basic_string_view<Char> str;
template <typename... Args>
std::basic_string<Char> operator()(Args&&... args) const {
return format(str, std::forward<Args>(args)...);
template <typename... T>
std::basic_string<Char> operator()(T&&... args) const {
return vformat(str, fmt::make_args_checked<T...>(str, args...));
}
};
@@ -2790,10 +2849,6 @@ constexpr auto operator"" _format(const char* s, size_t n)
-> detail::udl_formatter<char> {
return {{s, n}};
}
constexpr auto operator"" _format(const wchar_t* s, size_t n)
-> detail::udl_formatter<wchar_t> {
return {{s, n}};
}
/**
\rst

View File

@@ -1,4 +1,4 @@
// Formatting library for C++ - experimental wchar_t support
// Formatting library for C++ - optional wchar_t support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
@@ -12,7 +12,7 @@
#include "format.h"
namespace fmt {
FMT_BEGIN_NAMESPACE
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
@@ -28,6 +28,13 @@ constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
return {args...};
}
inline namespace literals {
constexpr auto operator"" _format(const wchar_t* s, size_t n)
-> detail::udl_formatter<wchar_t> {
return {{s, n}};
}
} // namespace literals
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
wmemory_buffer buffer;
detail::vformat_to(buffer, fmt, args);
@@ -48,6 +55,7 @@ void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), make_wformat_args(args...));
}
} // namespace fmt
FMT_END_NAMESPACE
#endif // FMT_WCHAR_H_