diff --git a/include/fmt/core.h b/include/fmt/core.h index 74b9b08d..f038e792 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -371,6 +371,9 @@ template class basic_string_view { the size with ``std::char_traits::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::length(s)) {} diff --git a/include/fmt/format.h b/include/fmt/format.h index dc2531e8..f81ed97e 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2238,6 +2238,7 @@ enum class arg_id_kind { none, index, name }; // An argument reference. template 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 name) @@ -3525,9 +3526,21 @@ template struct udl_arg { } }; +// Converts string literals to basic_string_view. template -FMT_CONSTEXPR basic_string_view literal_to_view(const Char (&s)[N]) { - return {s, N - 1}; +FMT_CONSTEXPR basic_string_view 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 - ((std::char_traits::to_int_type(s[N - 1]) == 0) ? 1 : 0)}; +} + +// Converts string_view to basic_string_view. +template +FMT_CONSTEXPR basic_string_view compile_string_to_view( + const std_string_view& s) { + return {s.data(), s.size()}; } } // namespace internal @@ -3585,18 +3598,17 @@ FMT_CONSTEXPR internal::udl_arg 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; \ - FMT_MAYBE_UNUSED __VA_ARGS__ FMT_CONSTEXPR \ - operator fmt::basic_string_view() 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; \ + FMT_MAYBE_UNUSED __VA_ARGS__ FMT_CONSTEXPR \ + operator fmt::basic_string_view() const { \ + return fmt::internal::compile_string_to_view(s); \ + } \ + }; \ + return FMT_COMPILE_STRING(); \ }() /** diff --git a/test/format-test.cc b/test/format-test.cc index 4e553022..812c1dcc 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -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; @@ -1854,10 +1855,23 @@ TEST(FormatTest, UnpackedArgs) { struct string_like {}; fmt::string_view to_string_view(string_like) { return "foo"; } +constexpr char with_null[3] = {'{', '}', '\0'}; +constexpr char no_null[2] = {'{', '}'}; + 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())); + (void)with_null; + (void)no_null; +#if __cplusplus >= 201703L + EXPECT_EQ("42", fmt::format(FMT_STRING(with_null), 42)); + EXPECT_EQ("42", fmt::format(FMT_STRING(no_null), 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) {