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

Enable FMT_STRING() use with types other than string literals #1589

Closed
wants to merge 1 commit into from
Closed
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
14 changes: 9 additions & 5 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,9 @@ template <typename Char> class basic_string_view {
the size with ``std::char_traits<Char>::length``.
\endrst
*/
#if __cplusplus >= 201703L // C++17's char_traits::length() is constexpr.
FMT_CONSTEXPR
#endif
basic_string_view(const Char* s)
: data_(s), size_(std::char_traits<Char>::length(s)) {}

Expand All @@ -381,11 +384,12 @@ template <typename Char> class basic_string_view {
: data_(s.data()),
size_(s.size()) {}

template <
typename S,
FMT_ENABLE_IF(std::is_same<S, internal::std_string_view<Char>>::value)>
FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()),
size_(s.size()) {}
#if __cplusplus >= 201703L // C++17's std::string_view::size() is constexpr.
FMT_CONSTEXPR
#endif
basic_string_view(internal::std_string_view<Char> s)
FMT_NOEXCEPT : data_(s.data()),
size_(s.size()) {}

/** Returns a pointer to the string data. */
FMT_CONSTEXPR const Char* data() const { return data_; }
Expand Down
50 changes: 32 additions & 18 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -2236,6 +2236,7 @@ enum class arg_id_kind { none, index, name };
// An argument reference.
template <typename Char> struct arg_ref {
FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}

FMT_CONSTEXPR explicit arg_ref(int index)
: kind(arg_id_kind::index), val(index) {}
FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name)
Expand Down Expand Up @@ -2266,8 +2267,8 @@ struct dynamic_format_specs : basic_format_specs<Char> {
arg_ref<Char> precision_ref;
};

// Format spec handler that saves references to arguments representing dynamic
// width and precision to be resolved at formatting time.
// Format spec handler that saves references to arguments representing
// dynamic width and precision to be resolved at formatting time.
template <typename ParseContext>
class dynamic_specs_handler
: public specs_setter<typename ParseContext::char_type> {
Expand Down Expand Up @@ -2579,8 +2580,9 @@ FMT_CONSTEXPR void parse_format_string(basic_string_view<Char> format_str,
auto begin = format_str.data();
auto end = begin + format_str.size();
while (begin != end) {
// Doing two passes with memchr (one for '{' and another for '}') is up to
// 2.5x faster than the naive one-pass implementation on big format strings.
// Doing two passes with memchr (one for '{' and another for '}') is up
// to 2.5x faster than the naive one-pass implementation on big format
// strings.
const Char* p = begin;
if (*begin != '{' && !find<IS_CONSTEXPR>(begin + 1, end, '{', p))
return write(begin, end);
Expand Down Expand Up @@ -3523,9 +3525,22 @@ template <typename Char> struct udl_arg {
}
};

// Converts string literals to basic_string_view.
template <typename Char, size_t N>
FMT_CONSTEXPR basic_string_view<Char> literal_to_view(const Char (&s)[N]) {
return {s, N - 1};
FMT_CONSTEXPR basic_string_view<Char> compile_string_to_view(
const Char (&s)[N]) {
// Remove trailing null character if needed. Won't be present if this is used
// with raw character array (i.e. not defined as a string).
return {s, N - ((N > 0 && std::char_traits<Char>::to_int_type(s[N - 1]) == 0)
? 1
: 0)};
}

// Converts string_view to basic_string_view.
template <typename Char>
FMT_CONSTEXPR basic_string_view<Char> compile_string_to_view(
const std_string_view<Char>& s) {
return {s.data(), s.size()};
}
} // namespace internal

Expand Down Expand Up @@ -3583,18 +3598,17 @@ FMT_CONSTEXPR internal::udl_arg<wchar_t> operator"" _a(const wchar_t* s,
#endif // FMT_USE_USER_DEFINED_LITERALS
FMT_END_NAMESPACE

#define FMT_STRING_IMPL(s, ...) \
[] { \
/* Use a macro-like name to avoid shadowing warnings. */ \
struct FMT_COMPILE_STRING : fmt::compile_string { \
using char_type = fmt::remove_cvref_t<decltype(*s)>; \
FMT_MAYBE_UNUSED __VA_ARGS__ FMT_CONSTEXPR \
operator fmt::basic_string_view<char_type>() const { \
/* FMT_STRING only accepts string literals. */ \
return fmt::internal::literal_to_view(s); \
} \
}; \
return FMT_COMPILE_STRING(); \
#define FMT_STRING_IMPL(s, ...) \
[] { \
/* Use a macro-like name to avoid shadowing warnings. */ \
struct FMT_COMPILE_STRING : fmt::compile_string { \
using char_type = fmt::remove_cvref_t<decltype(s[0])>; \
FMT_MAYBE_UNUSED __VA_ARGS__ FMT_CONSTEXPR \
operator fmt::basic_string_view<char_type>() const { \
return fmt::internal::compile_string_to_view<char_type>(s); \
} \
}; \
return FMT_COMPILE_STRING(); \
}()

/**
Expand Down
16 changes: 16 additions & 0 deletions test/format-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ using fmt::format_error;
using fmt::memory_buffer;
using fmt::string_view;
using fmt::wmemory_buffer;
using fmt::wstring_view;
using fmt::internal::basic_writer;
using fmt::internal::max_value;

Expand Down Expand Up @@ -1853,10 +1854,25 @@ TEST(FormatTest, UnpackedArgs) {
struct string_like {};
fmt::string_view to_string_view(string_like) { return "foo"; }

#if __cplusplus >= 201703L
namespace {
FMT_CONSTEXPR char withNull[3] = {'{', '}', '\0'};
FMT_CONSTEXPR char noNull[2] = {'{', '}'};
} // namespace
#endif

TEST(FormatTest, CompileTimeString) {
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42));
EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42));
EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like()));
#if __cplusplus >= 201703L
EXPECT_EQ("42", fmt::format(FMT_STRING(withNull), 42));
EXPECT_EQ("42", fmt::format(FMT_STRING(noNull), 42));
#endif
#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L
EXPECT_EQ("42", fmt::format(FMT_STRING(std::string_view("{}")), 42));
EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
#endif
}

TEST(FormatTest, CustomFormatCompileTimeString) {
Expand Down