Skip to content

Commit bd45154

Browse files
committed
[libc++] Implements Runtime format strings II.
Implements - P2918R2 Runtime format strings II
1 parent 9ebe6e2 commit bd45154

File tree

11 files changed

+334
-3
lines changed

11 files changed

+334
-3
lines changed

libcxx/docs/ReleaseNotes/18.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Implemented Papers
5050
- P0053R7 - C++ Synchronized Buffered Ostream (in the experimental library)
5151
- P2467R1 - Support exclusive mode for fstreams
5252
- P0020R6 - Floating Point Atomic
53+
- P2918R2 - Runtime format strings II
5354

5455

5556
Improvements and New Features

libcxx/docs/Status/Cxx2cPapers.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"`P2407R5 <https://wg21.link/P2407R5>`__","LWG","Freestanding Library: Partial Classes","Kona November 2023","","",""
3232
"`P2546R5 <https://wg21.link/P2546R5>`__","LWG","Debugging Support","Kona November 2023","","",""
3333
"`P2905R2 <https://wg21.link/P2905R2>`__","LWG","Runtime format strings","Kona November 2023","","","|format| |DR|"
34-
"`P2918R2 <https://wg21.link/P2918R2>`__","LWG","Runtime format strings II","Kona November 2023","","","|format|"
34+
"`P2918R2 <https://wg21.link/P2918R2>`__","LWG","Runtime format strings II","Kona November 2023","|Complete|","18.0","|format|"
3535
"`P2909R4 <https://wg21.link/P2909R4>`__","LWG","Fix formatting of code units as integers (Dude, where’s my ``char``?)","Kona November 2023","","","|format| |DR|"
3636
"`P0952R2 <https://wg21.link/P0952R2>`__","LWG","A new specification for ``std::generate_canonical``","Kona November 2023","","",""
3737
"`P2447R6 <https://wg21.link/P2447R6>`__","LWG","``std::span`` over an initializer list","Kona November 2023","","",""

libcxx/docs/Status/FormatIssues.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Number,Name,Standard,Assignee,Status,First released version
1818
"`P2757R3 <https://wg21.link/P2757R3>`__","Type-checking format args","C++26","","",
1919
"`P2637R3 <https://wg21.link/P2637R3>`__","Member ``visit``","C++26","","",
2020
"`P2905R2 <https://wg21.link/P2905R2>`__","Runtime format strings","C++26 DR","Mark de Wever","|In Progress|"
21-
"`P2918R2 <https://wg21.link/P2918R2>`__","Runtime format strings II","C++26","Mark de Wever","|In Progress|"
21+
"`P2918R2 <https://wg21.link/P2918R2>`__","Runtime format strings II","C++26","Mark de Wever","|Complete|",18.0
2222
"`P2909R4 <https://wg21.link/P2909R4>`__","Fix formatting of code units as integers (Dude, where’s my ``char``?)","C++26 DR","Mark de Wever","|In Progress|"
2323
`P1361 <https://wg21.link/P1361>`_,"Integration of chrono with text formatting","C++20",Mark de Wever,|In Progress|,
2424
`P2372 <https://wg21.link/P2372>`__,"Fixing locale handling in chrono formatters","C++20",Mark de Wever,|In Progress|,

libcxx/include/__format/format_functions.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,30 @@ __vformat_to(_ParseCtx&& __parse_ctx, _Ctx&& __ctx) {
338338

339339
} // namespace __format
340340

341+
# if _LIBCPP_STD_VER >= 26
342+
template <class _CharT>
343+
struct _LIBCPP_TEMPLATE_VIS __runtime_format_string {
344+
private:
345+
basic_string_view<_CharT> __str_;
346+
347+
template <class _Cp, class... _Args>
348+
friend struct _LIBCPP_TEMPLATE_VIS basic_format_string;
349+
350+
public:
351+
_LIBCPP_HIDE_FROM_ABI __runtime_format_string(basic_string_view<_CharT> __s) noexcept : __str_(__s) {}
352+
353+
__runtime_format_string(const __runtime_format_string&) = delete;
354+
__runtime_format_string& operator=(const __runtime_format_string&) = delete;
355+
};
356+
357+
_LIBCPP_HIDE_FROM_ABI inline __runtime_format_string<char> runtime_format(string_view __fmt) noexcept { return __fmt; }
358+
# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
359+
_LIBCPP_HIDE_FROM_ABI inline __runtime_format_string<wchar_t> runtime_format(wstring_view __fmt) noexcept {
360+
return __fmt;
361+
}
362+
# endif
363+
# endif //_LIBCPP_STD_VER >= 26
364+
341365
template <class _CharT, class... _Args>
342366
struct _LIBCPP_TEMPLATE_VIS basic_format_string {
343367
template <class _Tp>
@@ -350,6 +374,9 @@ struct _LIBCPP_TEMPLATE_VIS basic_format_string {
350374
_LIBCPP_HIDE_FROM_ABI constexpr basic_string_view<_CharT> get() const noexcept {
351375
return __str_;
352376
}
377+
# if _LIBCPP_STD_VER >= 26
378+
_LIBCPP_HIDE_FROM_ABI basic_format_string(__runtime_format_string<_CharT> __s) noexcept : __str_(__s.__str_) {}
379+
# endif
353380

354381
private:
355382
basic_string_view<_CharT> __str_;

libcxx/include/format

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ namespace std {
3131
3232
public:
3333
template<class T> consteval basic_format_string(const T& s);
34+
basic_format_string(runtime-format-string<charT> s) noexcept : str(s.str) {} // since C++26
3435
3536
constexpr basic_string_view<charT> get() const noexcept { return str; }
3637
};
@@ -41,6 +42,24 @@ namespace std {
4142
using wformat_string = // since C++23, exposition only before C++23
4243
basic_format_string<wchar_t, type_identity_t<Args>...>;
4344
45+
template<class charT> struct runtime-format-string { // since C++26, exposition-only
46+
private:
47+
basic_string_view<charT> str; // exposition-only
48+
49+
public:
50+
runtime-format-string(basic_string_view<charT> s) noexcept : str(s) {}
51+
52+
runtime-format-string(const runtime-format-string&) = delete;
53+
runtime-format-string& operator=(const runtime-format-string&) = delete;
54+
};
55+
56+
runtime-format-string<char> runtime_format(string_view fmt) noexcept {
57+
return fmt;
58+
}
59+
runtime-format-string<wchar_t> runtime_format(wstring_view fmt) noexcept {
60+
return fmt;
61+
}
62+
4463
// [format.functions], formatting functions
4564
template<class... Args>
4665
string format(format-string<Args...> fmt, Args&&... args);

libcxx/modules/std/format.inc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export namespace std {
2828
#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
2929
using std::wformat_string;
3030
#endif
31+
#if _LIBCPP_STD_VER >= 26
32+
using std::runtime_format;
33+
#endif //_LIBCPP_STD_VER >= 26
3134

3235
// [format.functions], formatting functions
3336
using std::format;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
10+
11+
// <format>
12+
13+
// template<class charT, class... Args>
14+
// class basic_format_string<charT, type_identity_t<Args>...>
15+
//
16+
// basic_format_string(runtime-format-string<charT> s) noexcept : str(s.str) {}
17+
//
18+
// Additional testing is done in
19+
// - libcxx/test/std/utilities/format/format.functions/format.runtime_format.pass.cpp
20+
// - libcxx/test/std/utilities/format/format.functions/format.locale.runtime_format.pass.cpp
21+
22+
#include <format>
23+
#include <cassert>
24+
25+
#include "test_macros.h"
26+
27+
int main(int, char**) {
28+
static_assert(noexcept(std::format_string<>{std::runtime_format(std::string_view{})}));
29+
{
30+
std::format_string<> s = std::runtime_format("}{invalid format string}{");
31+
assert(s.get() == "}{invalid format string}{");
32+
}
33+
34+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
35+
static_assert(noexcept(std::wformat_string<>{std::runtime_format(std::wstring_view{})}));
36+
{
37+
std::wformat_string<> s = std::runtime_format(L"}{invalid format string}{");
38+
assert(s.get() == L"}{invalid format string}{");
39+
}
40+
#endif // TEST_HAS_NO_WIDE_CHARACTERS
41+
42+
return 0;
43+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//===----------------------------------------------------------------------===//
2+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3+
// See https://llvm.org/LICENSE.txt for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
//
6+
//===----------------------------------------------------------------------===//
7+
8+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
9+
// UNSUPPORTED: no-localization
10+
// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
11+
12+
// XFAIL: availability-fp_to_chars-missing
13+
14+
// <format>
15+
16+
// Tests the behavior of
17+
//
18+
// runtime-format-string<char> runtime_format(string_view fmt) noexcept;
19+
// runtime-format-string<wchar_t> runtime_format(wstring_view fmt) noexcept;
20+
//
21+
// and
22+
//
23+
// template<class charT, class... Args>
24+
// struct basic_format_string {
25+
// ...
26+
// basic_format_string(runtime-format-string<charT> s) noexcept : str(s.str) {}
27+
// ...
28+
// }
29+
//
30+
// This is done by testing it in the top-level functions:
31+
//
32+
// template<class... Args>
33+
// string format(const locale& loc, format_string<Args...> fmt, Args&&... args);
34+
// template<class... Args>
35+
// wstring format(const locale& loc, wformat_string<Args...> fmt, Args&&... args);
36+
//
37+
// The basics of runtime_format and basic_format_string's constructor are tested in
38+
// - libcxx/test/std/utilities/format/format.syn/runtime_format_string.pass.cpp
39+
// - libcxx/test/std/utilities/format/format.fmt.string/ctor.runtime-format-string.pass.cpp
40+
41+
#include <format>
42+
#include <cassert>
43+
#include <locale>
44+
#include <vector>
45+
46+
#include "test_macros.h"
47+
#include "format_tests.h"
48+
#include "string_literal.h"
49+
#include "assert_macros.h"
50+
#include "concat_macros.h"
51+
52+
auto test = []<class CharT, class... Args>(
53+
std::basic_string_view<CharT> expected, std::basic_string_view<CharT> fmt, Args&&... args) constexpr {
54+
std::basic_string<CharT> out = std::format(std::locale(), std::runtime_format(fmt), std::forward<Args>(args)...);
55+
TEST_REQUIRE(out == expected,
56+
TEST_WRITE_CONCATENATED(
57+
"\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n'));
58+
};
59+
60+
auto test_exception =
61+
[]<class CharT, class... Args>(
62+
[[maybe_unused]] std::string_view what,
63+
[[maybe_unused]] std::basic_string_view<CharT> fmt,
64+
[[maybe_unused]] Args&&... args) {
65+
TEST_VALIDATE_EXCEPTION(
66+
std::format_error,
67+
[&]([[maybe_unused]] const std::format_error& e) {
68+
TEST_LIBCPP_REQUIRE(
69+
e.what() == what,
70+
TEST_WRITE_CONCATENATED(
71+
"\nFormat string ", fmt, "\nExpected exception ", what, "\nActual exception ", e.what(), '\n'));
72+
},
73+
TEST_IGNORE_NODISCARD std::format(std::locale(), std::runtime_format(fmt), std::forward<Args>(args)...));
74+
};
75+
76+
int main(int, char**) {
77+
format_tests<char, execution_modus::partial>(test, test_exception);
78+
79+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
80+
format_tests_char_to_wchar_t(test);
81+
format_tests<wchar_t, execution_modus::partial>(test, test_exception);
82+
#endif
83+
84+
return 0;
85+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//===----------------------------------------------------------------------===//
2+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3+
// See https://llvm.org/LICENSE.txt for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
//
6+
//===----------------------------------------------------------------------===//
7+
8+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
9+
// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME
10+
11+
// XFAIL: availability-fp_to_chars-missing
12+
13+
// <format>
14+
15+
// Tests the behavior of
16+
//
17+
// runtime-format-string<char> runtime_format(string_view fmt) noexcept;
18+
// runtime-format-string<wchar_t> runtime_format(wstring_view fmt) noexcept;
19+
//
20+
// and
21+
//
22+
// template<class charT, class... Args>
23+
// struct basic_format_string {
24+
// ...
25+
// basic_format_string(runtime-format-string<charT> s) noexcept : str(s.str) {}
26+
// ...
27+
// }
28+
//
29+
// This is done by testing it in the top-level functions:
30+
//
31+
// template<class... Args>
32+
// string format(format_string<Args...> fmt, Args&&... args);
33+
// template<class... Args>
34+
// wstring format(wformat_string<Args...> fmt, Args&&... args);
35+
//
36+
// The basics of runtime_format and basic_format_string's constructor are tested in
37+
// - libcxx/test/std/utilities/format/format.syn/runtime_format_string.pass.cpp
38+
// - libcxx/test/std/utilities/format/format.fmt.string/ctor.runtime-format-string.pass.cpp
39+
40+
#include <format>
41+
#include <cassert>
42+
#include <vector>
43+
44+
#include "test_macros.h"
45+
#include "format_tests.h"
46+
#include "string_literal.h"
47+
#include "assert_macros.h"
48+
#include "concat_macros.h"
49+
50+
auto test = []<class CharT, class... Args>(
51+
std::basic_string_view<CharT> expected, std::basic_string_view<CharT> fmt, Args&&... args) constexpr {
52+
std::basic_string<CharT> out = std::format(std::runtime_format(fmt), std::forward<Args>(args)...);
53+
TEST_REQUIRE(out == expected,
54+
TEST_WRITE_CONCATENATED(
55+
"\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n'));
56+
};
57+
58+
auto test_exception =
59+
[]<class CharT, class... Args>(
60+
[[maybe_unused]] std::string_view what,
61+
[[maybe_unused]] std::basic_string_view<CharT> fmt,
62+
[[maybe_unused]] Args&&... args) {
63+
TEST_VALIDATE_EXCEPTION(
64+
std::format_error,
65+
[&]([[maybe_unused]] const std::format_error& e) {
66+
TEST_LIBCPP_REQUIRE(
67+
e.what() == what,
68+
TEST_WRITE_CONCATENATED(
69+
"\nFormat string ", fmt, "\nExpected exception ", what, "\nActual exception ", e.what(), '\n'));
70+
},
71+
TEST_IGNORE_NODISCARD std::format(std::runtime_format(fmt), std::forward<Args>(args)...));
72+
};
73+
74+
int main(int, char**) {
75+
format_tests<char, execution_modus::partial>(test, test_exception);
76+
77+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
78+
format_tests_char_to_wchar_t(test);
79+
format_tests<wchar_t, execution_modus::partial>(test, test_exception);
80+
#endif
81+
82+
return 0;
83+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23
10+
11+
// <format>
12+
13+
// template<class charT> struct runtime-format-string { // exposition-only
14+
// private:
15+
// basic_string_view<charT> str; // exposition-only
16+
//
17+
// public:
18+
// runtime-format-string(basic_string_view<charT> s) noexcept : str(s) {}
19+
//
20+
// runtime-format-string(const runtime-format-string&) = delete;
21+
// runtime-format-string& operator=(const runtime-format-string&) = delete;
22+
// };
23+
//
24+
// runtime-format-string<char> runtime_format(string_view fmt) noexcept;
25+
// runtime-format-string<wchar_t> runtime_format(wstring_view fmt) noexcept;
26+
//
27+
// Additional testing is done in
28+
// - libcxx/test/std/utilities/format/format.functions/format.runtime_format.pass.cpp
29+
// - libcxx/test/std/utilities/format/format.functions/format.locale.runtime_format.pass.cpp
30+
31+
#include <format>
32+
33+
#include <cassert>
34+
#include <concepts>
35+
#include <string_view>
36+
#include <type_traits>
37+
38+
#include "test_macros.h"
39+
40+
template <class T, class CharT>
41+
static void test_properties() {
42+
static_assert(std::is_nothrow_convertible_v<std::basic_string_view<CharT>, T>);
43+
static_assert(std::is_nothrow_constructible_v<T, std::basic_string_view<CharT>>);
44+
45+
static_assert(!std::copy_constructible<T>);
46+
static_assert(!std::is_copy_assignable_v<T>);
47+
48+
static_assert(!std::move_constructible<T>);
49+
static_assert(!std::is_move_assignable_v<T>);
50+
}
51+
52+
int main(int, char**) {
53+
static_assert(noexcept(std::runtime_format(std::string_view{})));
54+
auto format_string = std::runtime_format(std::string_view{});
55+
56+
using FormatString = decltype(format_string);
57+
LIBCPP_ASSERT((std::same_as<FormatString, std::__runtime_format_string<char>>));
58+
test_properties<FormatString, char>();
59+
60+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
61+
static_assert(noexcept(std::runtime_format(std::wstring_view{})));
62+
auto wformat_string = std::runtime_format(std::wstring_view{});
63+
64+
using WFormatString = decltype(wformat_string);
65+
LIBCPP_ASSERT((std::same_as<WFormatString, std::__runtime_format_string<wchar_t>>));
66+
test_properties<WFormatString, wchar_t>();
67+
#endif // TEST_HAS_NO_WIDE_CHARACTERS
68+
69+
return 0;
70+
}

0 commit comments

Comments
 (0)