Skip to content

Commit 7c25aef

Browse files
<format>: Copy basic_format_args in tuple formatters when needed (#4681)
Co-authored-by: Stephan T. Lavavej <[email protected]>
1 parent 0d32e40 commit 7c25aef

File tree

3 files changed

+119
-20
lines changed

3 files changed

+119
-20
lines changed

stl/inc/format

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2447,11 +2447,17 @@ public:
24472447
}
24482448
};
24492449

2450-
using _Fmt_it = back_insert_iterator<_Fmt_buffer<char>>;
2451-
using _Fmt_wit = back_insert_iterator<_Fmt_buffer<wchar_t>>;
2450+
template <class _CharT>
2451+
using _Basic_fmt_it = back_insert_iterator<_Fmt_buffer<_CharT>>;
2452+
2453+
using _Fmt_it = _Basic_fmt_it<char>;
2454+
using _Fmt_wit = _Basic_fmt_it<wchar_t>;
2455+
2456+
template <class _CharT>
2457+
using _Default_format_context = basic_format_context<_Basic_fmt_it<_CharT>, _CharT>;
24522458

2453-
_EXPORT_STD using format_context = basic_format_context<_Fmt_it, char>;
2454-
_EXPORT_STD using wformat_context = basic_format_context<_Fmt_wit, wchar_t>;
2459+
_EXPORT_STD using format_context = _Default_format_context<char>;
2460+
_EXPORT_STD using wformat_context = _Default_format_context<wchar_t>;
24552461

24562462
#if _HAS_CXX23
24572463
_EXPORT_STD enum class range_format { disabled, map, set, sequence, string, debug_string };
@@ -4107,6 +4113,21 @@ private:
41074113

41084114
_Fill_align_and_width_specs<_CharT> _Specs;
41094115

4116+
template <class _FormatContext, class... _ArgTypes>
4117+
void _Format_to_context(_FormatContext& _Fmt_ctx, _ArgTypes&... _Args) const {
4118+
_STD _Copy_unchecked(_Opening_bracket._Unchecked_begin(), _Opening_bracket._Unchecked_end(), _Fmt_ctx.out());
4119+
[&]<size_t... _Indices>(index_sequence<_Indices...>) {
4120+
auto _Single_writer = [&]<size_t _Idx>(auto& _Arg) {
4121+
if constexpr (_Idx != 0) {
4122+
_STD _Copy_unchecked(_Separator._Unchecked_begin(), _Separator._Unchecked_end(), _Fmt_ctx.out());
4123+
}
4124+
_STD get<_Idx>(_Underlying).format(_Arg, _Fmt_ctx);
4125+
};
4126+
(_Single_writer.template operator()<_Indices>(_Args), ...);
4127+
}(index_sequence_for<_ArgTypes...>{});
4128+
_STD _Copy_unchecked(_Closing_bracket._Unchecked_begin(), _Closing_bracket._Unchecked_end(), _Fmt_ctx.out());
4129+
}
4130+
41104131
protected:
41114132
static constexpr bool _Is_const_formattable = (formattable<const _Types, _CharT> && ...);
41124133

@@ -4121,26 +4142,26 @@ protected:
41214142
_STD _Get_dynamic_specs<_Width_checker>(_Fmt_ctx.arg(static_cast<size_t>(_Specs._Dynamic_width_index)));
41224143
}
41234144

4124-
basic_string<_CharT> _Tmp_buf;
4125-
auto _Tmp_ctx = basic_format_context<back_insert_iterator<basic_string<_CharT>>, _CharT>::_Make_from(
4126-
_STD back_inserter(_Tmp_buf), {}, _Fmt_ctx._Get_lazy_locale());
4145+
if (_Format_specs._Width <= 0) {
4146+
_Format_to_context(_Fmt_ctx, _Args...);
4147+
return _Fmt_ctx.out();
4148+
}
41274149

4128-
_STD _Copy_unchecked(_Opening_bracket._Unchecked_begin(), _Opening_bracket._Unchecked_end(), _Tmp_ctx.out());
4129-
[&]<size_t... _Indices>(index_sequence<_Indices...>) {
4130-
auto _Single_writer = [&]<size_t _Idx>(auto& _Arg) {
4131-
if constexpr (_Idx != 0) {
4132-
_STD _Copy_unchecked(_Separator._Unchecked_begin(), _Separator._Unchecked_end(), _Tmp_ctx.out());
4133-
}
4134-
_STD get<_Idx>(_Underlying).format(_Arg, _Tmp_ctx);
4135-
};
4136-
(_Single_writer.template operator()<_Indices>(_Args), ...);
4137-
}(index_sequence_for<_ArgTypes...>{});
4138-
_STD _Copy_unchecked(_Closing_bracket._Unchecked_begin(), _Closing_bracket._Unchecked_end(), _Tmp_ctx.out());
4150+
basic_string<_CharT> _Tmp_str;
4151+
if constexpr (is_same_v<_FormatContext, _Default_format_context<_CharT>>) {
4152+
_Fmt_iterator_buffer<back_insert_iterator<basic_string<_CharT>>, _CharT> _Tmp_buf{
4153+
back_insert_iterator{_Tmp_str}};
4154+
auto _Tmp_ctx = _FormatContext::_Make_from(
4155+
_Basic_fmt_it<_CharT>{_Tmp_buf}, _Fmt_ctx._Get_args(), _Fmt_ctx._Get_lazy_locale());
4156+
_Format_to_context(_Tmp_ctx, _Args...);
4157+
} else {
4158+
_CSTD abort(); // no basic_format_context object other than _Default_format_context can be created
4159+
}
41394160

4140-
const int _Width = _Measure_display_width<_CharT>(_Tmp_buf);
4161+
const int _Width = _Measure_display_width<_CharT>(_Tmp_str);
41414162
return _STD _Write_aligned(
41424163
_Fmt_ctx.out(), _Width, _Format_specs, _Fmt_align::_Left, [&](typename _FormatContext::iterator _Out) {
4143-
return _STD _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{_Tmp_buf});
4164+
return _STD _Fmt_write(_STD move(_Out), basic_string_view<_CharT>{_Tmp_str});
41444165
});
41454166
}
41464167

tests/std/tests/P2286R8_text_formatting_tuple/test.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <concepts>
1919
#include <cstddef>
2020
#include <format>
21+
#include <iterator>
2122
#include <string>
2223
#include <string_view>
2324
#include <tuple>
@@ -435,10 +436,32 @@ auto test_vformat_exception = []<class CharT, class... Args>([[maybe_unused]] st
435436
}
436437
};
437438

439+
// Also test that functions taking non-constructible basic_format_context specializations can be well-formed,
440+
// despite the fact that they can't actually be called.
441+
442+
template <class CharT>
443+
void test_unconstructible_format_context_for_raw_ptr(basic_format_context<CharT*, CharT>& ctx) { // COMPILE-ONLY
444+
formatter<tuple<basic_string<CharT>>, CharT> tuple_formatter;
445+
tuple_formatter.format(make_tuple(basic_string<CharT>(STR("42"))), ctx);
446+
}
447+
448+
template <class CharT>
449+
void test_unconstructible_format_context_for_back_inserter(
450+
basic_format_context<back_insert_iterator<basic_string<CharT>>, CharT>& ctx) { // COMPILE-ONLY
451+
formatter<tuple<basic_string<CharT>>, CharT> tuple_formatter;
452+
tuple_formatter.format(make_tuple(basic_string<CharT>(STR("42"))), ctx);
453+
}
454+
438455
int main() {
439456
run_tests<char>(test_format, test_format_exception);
440457
run_tests<char>(test_vformat, test_vformat_exception);
441458

442459
run_tests<wchar_t>(test_format, test_format_exception);
443460
run_tests<wchar_t>(test_vformat, test_vformat_exception);
461+
462+
(void) &test_unconstructible_format_context_for_raw_ptr<char>;
463+
(void) &test_unconstructible_format_context_for_raw_ptr<wchar_t>;
464+
465+
(void) &test_unconstructible_format_context_for_back_inserter<char>;
466+
(void) &test_unconstructible_format_context_for_back_inserter<wchar_t>;
444467
}

tests/std/tests/P2693R1_text_formatting_thread_id/test.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@
55
#include <array>
66
#include <cassert>
77
#include <concepts>
8+
#include <cstddef>
89
#include <execution>
910
#include <ios>
1011
#include <locale>
1112
#include <ranges>
1213
#include <sstream>
1314
#include <string>
15+
#include <string_view>
1416
#include <thread>
17+
#include <tuple>
1518
#include <type_traits>
19+
#include <utility>
20+
#include <variant>
1621

1722
#include "test_format_support.hpp"
1823

@@ -254,12 +259,62 @@ void check_invalid_specs() {
254259
}
255260
}
256261

262+
// Also test GH-4651 "<format>: Underlying formatters of pair-or-tuple formatter cannot access format args"
263+
264+
template <class CharT>
265+
using default_format_parse_context = conditional_t<is_same_v<CharT, char>, format_parse_context,
266+
conditional_t<is_same_v<CharT, wchar_t>, wformat_parse_context, void>>;
267+
268+
template <size_t I>
269+
struct substitute_arg {};
270+
271+
template <size_t I, class CharT>
272+
struct std::formatter<substitute_arg<I>, CharT> {
273+
template <class ParseContext>
274+
constexpr auto parse(ParseContext& ctx) {
275+
auto it = ctx.begin();
276+
if (it != ctx.end() && *it != '}') {
277+
throw format_error{"Expected empty spec"};
278+
}
279+
280+
ctx.check_arg_id(I);
281+
return it;
282+
}
283+
284+
template <class FormatContext>
285+
auto format(substitute_arg<I>, FormatContext& ctx) const {
286+
auto visitor = [&]<class T>(T val) -> FormatContext::iterator {
287+
if constexpr (same_as<T, monostate>) {
288+
return ranges::copy(STR("monostate"sv), ctx.out()).out;
289+
} else if constexpr (same_as<T, typename basic_format_arg<FormatContext>::handle>) {
290+
default_format_parse_context<CharT> parse_ctx{STR("")};
291+
val.format(parse_ctx, ctx);
292+
return ctx.out();
293+
} else {
294+
return format_to(ctx.out(), STR("{}"), val);
295+
}
296+
};
297+
298+
return visit_format_arg(visitor, ctx.arg(I));
299+
}
300+
};
301+
302+
template <class CharT>
303+
void check_substitute_arg_with_tuple_formatters() {
304+
assert(format(STR("{0:}"), tuple{substitute_arg<1>{}, substitute_arg<2>{}}, STR("thread::id"), thread::id{})
305+
== STR("(thread::id, 0)"));
306+
assert(format(STR("{0:}"), pair{substitute_arg<1>{}, substitute_arg<2>{}}, STR("thread::id"), thread::id{})
307+
== STR("(thread::id, 0)"));
308+
}
309+
257310
template <class CharT>
258311
void test() {
259312
check_formatting_of_default_constructed_thread_id<CharT, FormatFn>();
260313
check_formatting_of_default_constructed_thread_id<CharT, VFormatFn>();
261314
check_formatting_of_default_constructed_thread_id<CharT, MoveOnlyFormat>();
262315

316+
check_substitute_arg_with_tuple_formatters<CharT>();
317+
263318
const array checks = {
264319
// NB: those functions call 'this_thread::get_id' - let's check various ids
265320
check_formatting_of_this_thread_id<CharT, FormatFn>,

0 commit comments

Comments
 (0)