diff --git a/stl/inc/format b/stl/inc/format index f968858c563..83631ab8089 100644 --- a/stl/inc/format +++ b/stl/inc/format @@ -333,11 +333,11 @@ public: // While the standard presents an exposition only enum value for // the indexing mode (manual, automatic, or unknown) we use _Next_arg_id to indicate it. - // _Next_arg_id == 0 means unknown // _Next_arg_id > 0 means automatic - // _Next_arg_id == -1 means manual + // _Next_arg_id == 0 means unknown + // _Next_arg_id < 0 means manual constexpr size_t next_arg_id() { - if (_Next_arg_id == -1) { + if (_Next_arg_id < 0) { throw format_error("Can not switch from manual to automatic indexing"); } @@ -366,58 +366,365 @@ private: ptrdiff_t _Next_arg_id = 0; }; -// TODO: test coverage +enum class _Basic_format_arg_type : uint8_t { + _None, + _Int_type, + _UInt_type, + _Long_long_type, + _ULong_long_type, + _Bool_type, + _Char_type, + _Float_type, + _Double_type, + _Long_double_type, + _Pointer_type, + _CString_type, + _String_type, + _Custom_type, +}; +static_assert(static_cast(_Basic_format_arg_type::_Custom_type) <= 16); + template class basic_format_arg { public: - class handle; + using _CharType = typename _Context::char_type; -private: - using _Char_type = typename _Context::char_type; - - using _Format_arg_value = variant, handle>; - - _Format_arg_value _Value; - -public: class handle { private: const void* _Ptr; - void (*_Format)(basic_format_parse_context<_Char_type>& _Parse_ctx, _Context _Format_ctx, const void*); + void (*_Format)(basic_format_parse_context<_CharType>& _Parse_ctx, _Context _Format_ctx, const void*); friend basic_format_arg; public: - void format(basic_format_parse_context<_Char_type>& _Parse_ctx, _Context& _Format_ctx) { + template + explicit handle(const _Ty& _Val) noexcept + : _Ptr(_STD addressof(_Val)), + _Format([](basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx, const void* _Ptr) { + typename _Context::template formatter_type<_Ty> _Formatter; + _Parse_ctx.advance_to(_Formatter.parse(_Parse_ctx)); + _Format_ctx.advance_to(_Formatter.format(*static_cast(_Ptr), _Format_ctx)); + }) {} + + void format(basic_format_parse_context<_CharType>& _Parse_ctx, _Context& _Format_ctx) { _Format(_Parse_ctx, _Format_ctx, _Ptr); } }; - basic_format_arg() noexcept = default; + // TRANSITION, LLVM-49072 + basic_format_arg() noexcept : _Active_state(_Basic_format_arg_type::_None), _No_state() {} + + explicit basic_format_arg(const int _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Int_type), _Int_state(_Val) {} + explicit basic_format_arg(const unsigned int _Val) noexcept + : _Active_state(_Basic_format_arg_type::_UInt_type), _UInt_state(_Val) {} + explicit basic_format_arg(const long long _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Long_long_type), _Long_long_state(_Val) {} + explicit basic_format_arg(const unsigned long long _Val) noexcept + : _Active_state(_Basic_format_arg_type::_ULong_long_type), _ULong_long_state(_Val) {} + explicit basic_format_arg(const bool _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Bool_type), _Bool_state(_Val) {} + explicit basic_format_arg(const _CharType _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Char_type), _Char_state(_Val) {} + explicit basic_format_arg(const float _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Float_type), _Float_state(_Val) {} + explicit basic_format_arg(const double _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Double_type), _Double_state(_Val) {} + explicit basic_format_arg(const long double _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Long_double_type), _Long_double_state(_Val) {} + explicit basic_format_arg(const void* _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Pointer_type), _Pointer_state(_Val) {} + explicit basic_format_arg(const _CharType* _Val) noexcept + : _Active_state(_Basic_format_arg_type::_CString_type), _CString_state(_Val) {} + explicit basic_format_arg(const basic_string_view<_CharType> _Val) noexcept + : _Active_state(_Basic_format_arg_type::_String_type), _String_state(_Val) {} + explicit basic_format_arg(const handle _Val) noexcept + : _Active_state(_Basic_format_arg_type::_Custom_type), _Custom_state(_Val) {} explicit operator bool() const noexcept { - return !_STD holds_alternative(_Value); + return _Active_state != _Basic_format_arg_type::_None; } + + _Basic_format_arg_type _Active_state = _Basic_format_arg_type::_None; + union { + monostate _No_state = monostate{}; + int _Int_state; + unsigned int _UInt_state; + long long _Long_long_state; + unsigned long long _ULong_long_state; + bool _Bool_state; + _CharType _Char_state; + float _Float_state; + double _Double_state; + long double _Long_double_state; + const void* _Pointer_state; + const _CharType* _CString_state; + basic_string_view<_CharType> _String_state; + handle _Custom_state; + }; }; -// TODO: test coverage +template +auto visit_format_arg(_Visitor&& _Vis, basic_format_arg<_Context> _Arg) { + switch (_Arg._Active_state) { + case _Basic_format_arg_type::_None: + return _Vis(_Arg._No_state); + case _Basic_format_arg_type::_Int_type: + return _Vis(_Arg._Int_state); + case _Basic_format_arg_type::_UInt_type: + return _Vis(_Arg._UInt_state); + case _Basic_format_arg_type::_Long_long_type: + return _Vis(_Arg._Long_long_state); + case _Basic_format_arg_type::_ULong_long_type: + return _Vis(_Arg._ULong_long_state); + case _Basic_format_arg_type::_Bool_type: + return _Vis(_Arg._Bool_state); + case _Basic_format_arg_type::_Char_type: + return _Vis(_Arg._Char_state); + case _Basic_format_arg_type::_Float_type: + return _Vis(_Arg._Float_state); + case _Basic_format_arg_type::_Double_type: + return _Vis(_Arg._Double_state); + case _Basic_format_arg_type::_Long_double_type: + return _Vis(_Arg._Long_double_state); + case _Basic_format_arg_type::_Pointer_type: + return _Vis(_Arg._Pointer_state); + case _Basic_format_arg_type::_CString_type: + return _Vis(_Arg._CString_state); + case _Basic_format_arg_type::_String_type: + return _Vis(_Arg._String_state); + case _Basic_format_arg_type::_Custom_type: + return _Vis(_Arg._Custom_state); + default: + _STL_VERIFY(false, "basic_format_arg is in impossible state"); + return _Vis(0); + } +} + +template +/* consteval */ constexpr auto _Get_format_arg_storage_type() noexcept { + using _CharType = typename _Context::char_type; + if constexpr (is_same_v<_Ty, monostate>) { + return monostate{}; + } else if constexpr (is_same_v<_Ty, _CharType>) { + return _CharType{}; + } else if constexpr (is_same_v<_Ty, char> && is_same_v<_CharType, wchar_t>) { + return _CharType{}; + } else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(int)) { + return int{}; + } else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned int)) { + return static_cast(42); + } else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(long long)) { + return static_cast(42); + } else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned long long)) { + return static_cast(42); + } else if constexpr (is_same_v<_Ty, bool>) { + return bool{}; + } else if constexpr (is_same_v<_Ty, float>) { + return float{}; + } else if constexpr (is_same_v<_Ty, double>) { + return double{}; + } else if constexpr (is_same_v<_Ty, long double>) { + return static_cast(42); + } else if constexpr (is_same_v<_Ty, const void*>) { + return static_cast(nullptr); + } else if constexpr (is_same_v<_Ty, const _CharType*>) { + return static_cast(nullptr); + } else if constexpr (is_same_v<_Ty, basic_string_view<_CharType>>) { + return basic_string_view<_CharType>{}; + } else { + return basic_format_arg<_Context>::handle(); + } +} + +template +inline constexpr size_t _Get_format_arg_storage_size = sizeof(_Get_format_arg_storage_type<_Context, _Ty>()); + +struct _Format_arg_store_packed_index { + // TRANSITION, Should be templated on number of arguments for even less storage + using _Index_type = size_t; + + constexpr _Format_arg_store_packed_index() = default; + constexpr explicit _Format_arg_store_packed_index(const size_t _Index) + : _Index(static_cast<_Index_type>(_Index)), _Type(_Basic_format_arg_type::_None) {} + + _Index_type _Index : (sizeof(_Index_type) * 8 - 4); + _Basic_format_arg_type _Type : 4; +}; + +template +class basic_format_args; + template class _Format_arg_store { - static constexpr size_t _Num_args = sizeof...(_Args); - // TODO: no arg packing yet +private: + using _CharType = typename _Context::char_type; + using _Index_type = _Format_arg_store_packed_index; + + friend basic_format_args<_Context>; + + static constexpr size_t _Num_args = sizeof...(_Args); + static constexpr size_t _Index_length = _Num_args * sizeof(_Index_type); + static constexpr size_t _Storage_length = (_Get_format_arg_storage_size<_Context, _Args> + ... + 0); + + // we store the data in memory as _Format_arg_store_packed_index[_Index_length] + unsigned char[_Storage_length] + unsigned char _Storage[_Index_length + _Storage_length]; + + template + void _Store_impl(const size_t _Arg_index, const _Basic_format_arg_type _Arg_type, _Ty _Val) noexcept { + const auto _Index_array = reinterpret_cast<_Index_type*>(_Storage); + const auto _Store_index = _Index_array[_Arg_index]._Index; + const auto _Length = _Get_format_arg_storage_size<_Context, _Ty>; + + _CSTD memcpy(_Storage + _Index_length + _Store_index, _STD addressof(_Val), _Length); + _Index_array[_Arg_index]._Type = _Arg_type; + if (_Arg_index + 1 < _Num_args) { + // Set the starting index of the next arg, as that is dynamic, must be called with increasing index + _Index_array[_Arg_index + 1] = _Format_arg_store_packed_index{_Store_index + _Length}; + } + } + + // See [format.arg]/5 + template + void _Store(const size_t _Arg_index, const _Ty& _Val) noexcept { + _Store_impl::handle>( + _Arg_index, _Basic_format_arg_type::_Custom_type, basic_format_arg<_Context>::handle(_Val)); + } + + // clang-format off + template + requires integral<_Ty> || floating_point<_Ty> + void _Store(const size_t _Arg_index, _Ty _Val) noexcept { + // clang-format on + if constexpr (is_same_v<_Ty, bool>) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Bool_type, _Val); + } else if constexpr (is_same_v<_Ty, _CharType>) { + _Store_impl<_CharType>(_Arg_index, _Basic_format_arg_type::_Char_type, _Val); + } else if constexpr (is_same_v<_Ty, char> && is_same_v<_CharType, wchar_t>) { + _Store_impl<_CharType>(_Arg_index, _Basic_format_arg_type::_Char_type, static_cast(_Val)); + } else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(int)) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Int_type, static_cast(_Val)); + } else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned int)) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_UInt_type, static_cast(_Val)); + } else if constexpr (signed_integral<_Ty> && sizeof(_Ty) <= sizeof(long long)) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Long_long_type, static_cast(_Val)); + } else if constexpr (unsigned_integral<_Ty> && sizeof(_Ty) <= sizeof(unsigned long long)) { + _Store_impl( + _Arg_index, _Basic_format_arg_type::_ULong_long_type, static_cast(_Val)); + } else if constexpr (is_same_v<_Ty, float>) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Float_type, _Val); + } else if constexpr (is_same_v<_Ty, double>) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Double_type, _Val); + } else if constexpr (is_same_v<_Ty, long double>) { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Long_double_type, _Val); + } else { + static_assert(_Always_false<_Ty>, "Invalid type passed to _Format_arg_store::_Store"); + } + } - using _Value_type = basic_format_arg<_Context>; + void _Store(const size_t _Arg_index, const _CharType* _Val) noexcept { + _Store_impl(_Arg_index, _Basic_format_arg_type::_CString_type, _Val); + } + + template + void _Store(const size_t _Arg_index, basic_string_view<_CharType, _Traits> _Val) noexcept { + _Store_impl>( + _Arg_index, _Basic_format_arg_type::_String_type, basic_string_view<_CharType>{_Val}); + } + + template + void _Store(const size_t _Arg_index, const basic_string<_CharType, _Traits, _Alloc>& _Val) noexcept { + _Store_impl>( + _Arg_index, _Basic_format_arg_type::_String_type, basic_string_view<_CharType>{_Val.data(), _Val.size()}); + } + + void _Store(const size_t _Arg_index, nullptr_t) noexcept { + _Store_impl(_Arg_index, _Basic_format_arg_type::_Pointer_type, static_cast(nullptr)); + } + + // clang-format off + template + requires is_void_v<_Ty> + void _Store(const size_t _Arg_index, const _Ty* _Ptr) noexcept { + // clang-format on + _Store_impl(_Arg_index, _Basic_format_arg_type::_Pointer_type, static_cast(_Ptr)); + } + +public: + _Format_arg_store(const _Args&... _Vals) noexcept { + // Note: _Storage is uninitialized, so manually initialize the first index + _STD construct_at(reinterpret_cast<_Format_arg_store_packed_index*>(_Storage)); + size_t _Arg_index = 0; + (_Store(_Arg_index++, _Vals), ...); + } }; -// TODO: test coverage +template +class _Format_arg_store<_Context> {}; + template class basic_format_args { +private: + template + _NODISCARD static auto _Get_value_from_memory(const unsigned char* _Val) noexcept { + auto& _Temp = *reinterpret_cast(_Val); + return _Bit_cast<_Ty>(_Temp); + } + public: basic_format_args() noexcept; + basic_format_args(const _Format_arg_store<_Context>&) noexcept {} template - basic_format_args(const _Format_arg_store<_Context, _Args...>& store) noexcept; + basic_format_args(const _Format_arg_store<_Context, _Args...>& _Store) noexcept + : _Num_args(sizeof...(_Args)), _Storage(_Store._Storage) {} + + basic_format_arg<_Context> get(const size_t _Index) const noexcept { + if (_Index >= _Num_args) { + return basic_format_arg<_Context>{}; + } - basic_format_arg<_Context> get(size_t _Index) const noexcept; + using _CharType = typename _Context::char_type; + const auto _Packed_index = reinterpret_cast(_Storage)[_Index]; + const auto _Index_length = _Num_args * sizeof(_Format_arg_store_packed_index); + const unsigned char* _Arg_storage = _Storage + _Index_length + _Packed_index._Index; + + switch (_Packed_index._Type) { + case _Basic_format_arg_type::_None: + return basic_format_arg<_Context>{}; + case _Basic_format_arg_type::_Int_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_UInt_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Long_long_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_ULong_long_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Bool_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Char_type: + return basic_format_arg<_Context>{_Get_value_from_memory<_CharType>(_Arg_storage)}; + case _Basic_format_arg_type::_Float_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Double_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Long_double_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_Pointer_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_CString_type: + return basic_format_arg<_Context>{_Get_value_from_memory(_Arg_storage)}; + case _Basic_format_arg_type::_String_type: + return basic_format_arg<_Context>{_Get_value_from_memory>(_Arg_storage)}; + case _Basic_format_arg_type::_Custom_type: + return basic_format_arg<_Context>{ + _Get_value_from_memory::handle>(_Arg_storage)}; + default: + _STL_ASSERT(false, "Invalid basic_format_arg type"); + return basic_format_arg<_Context>{}; + } + } + +private: + size_t _Num_args = 0; + const unsigned char* _Storage = nullptr; }; // TODO: test coverage @@ -459,6 +766,16 @@ using wformat_context = basic_format_context, wstr using format_args = basic_format_args; using wformat_args = basic_format_args; +template +auto make_format_args(const _Args&... _Vals) { + return _Format_arg_store<_Context, _Args...>{_Vals...}; +} + +template +auto make_wformat_args(const _Args&... _Vals) { + return _Format_arg_store<_Context, _Args...>{_Vals...}; +} + // FUNCTION vformat string vformat(string_view _Fmt, format_args _Args); wstring vformat(wstring_view _Fmt, wformat_args _Args); diff --git a/tests/std/test.lst b/tests/std/test.lst index 51f0673706c..1b777a88756 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -251,6 +251,7 @@ tests\P0595R2_is_constant_evaluated tests\P0607R0_inline_variables tests\P0616R0_using_move_in_numeric tests\P0631R8_numbers_math_constants +tests\P0645R10_text_formatting_args tests\P0645R10_text_formatting_death tests\P0645R10_text_formatting_parse_contexts tests\P0645R10_text_formatting_parsing diff --git a/tests/std/tests/P0645R10_text_formatting_args/env.lst b/tests/std/tests/P0645R10_text_formatting_args/env.lst new file mode 100644 index 00000000000..22f1f0230a4 --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_args/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\strict_winsdk_concepts_matrix.lst diff --git a/tests/std/tests/P0645R10_text_formatting_args/test.cpp b/tests/std/tests/P0645R10_text_formatting_args/test.cpp new file mode 100644 index 00000000000..08355b8a90f --- /dev/null +++ b/tests/std/tests/P0645R10_text_formatting_args/test.cpp @@ -0,0 +1,239 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +template +constexpr auto get_input_literal() { + if constexpr (same_as) { + return "meow"; + } else { + return L"meow"; + } +} + +template +constexpr auto get_input_sv() { + if constexpr (same_as) { + return "meow"sv; + } else { + return L"meow"sv; + } +} + +class context { +public: + using char_type = char; +}; + +class wcontext { +public: + using char_type = wchar_t; +}; + +enum class Arg_type : uint8_t { + none, + int_type, + unsigned_type, + long_long_type, + unsigned_long_long_type, + bool_type, + char_type, + float_type, + double_type, + long_double_type, + pointer_type, + string_literal_type, + string_type, + handle_type, +}; + +template +auto visitor = [](auto&& arg) { + using T = decay_t; + using char_type = typename Context::char_type; + if constexpr (is_same_v) { + return Arg_type::none; + } else if constexpr (is_same_v) { + return Arg_type::int_type; + } else if constexpr (is_same_v) { + return Arg_type::unsigned_type; + } else if constexpr (is_same_v) { + return Arg_type::long_long_type; + } else if constexpr (is_same_v) { + return Arg_type::unsigned_long_long_type; + } else if constexpr (is_same_v) { + return Arg_type::char_type; + } else if constexpr (is_same_v) { + return Arg_type::float_type; + } else if constexpr (is_same_v) { + return Arg_type::double_type; + } else if constexpr (is_same_v) { + return Arg_type::long_double_type; + } else if constexpr (is_same_v) { + return Arg_type::pointer_type; + } else if constexpr (is_same_v) { + return Arg_type::string_literal_type; + } else if constexpr (is_same_v>) { + return Arg_type::string_type; + } else { + return Arg_type::handle_type; + } +}; + +template +void test_basic_format_arg() { + using char_type = typename Context::char_type; + + { // construction + basic_format_arg default_constructed; + assert(!default_constructed); + + basic_format_arg from_int{5}; + assert(from_int); + + basic_format_arg from_unsigned{5u}; + assert(from_unsigned); + + basic_format_arg from_long_long{5ll}; + assert(from_long_long); + + basic_format_arg from_unsigned_long_long{5ull}; + assert(from_unsigned_long_long); + + basic_format_arg from_float{5.0f}; + assert(from_float); + + basic_format_arg from_double{5.0}; + assert(from_double); + + basic_format_arg from_long_double{5.0L}; + assert(from_long_double); + + basic_format_arg from_pointer{static_cast(nullptr)}; + assert(from_pointer); + + basic_format_arg from_literal{get_input_literal()}; + assert(from_literal); + + basic_format_arg from_string_view{get_input_sv()}; + assert(from_string_view); + + // TRANSITION, implement handle + // basic_format_arg from_handle{}; + // assert(from_handle); + } +} +template +void test_empty_format_arg() { + const auto store = make_format_args(); + const basic_format_args args{store}; + const same_as> auto first_arg = args.get(0); + assert(!first_arg); +} + +template +void test_single_format_arg(Type value) { + const auto store = make_format_args(value); + const basic_format_args args{store}; + const same_as> auto first_arg = args.get(0); + assert(first_arg); + assert(visit_format_arg(visitor, first_arg) == Result); + const same_as> auto other_arg = args.get(1); + assert(!other_arg); +} + +template +void test_format_arg_store() { + using char_type = typename Context::char_type; + + test_empty_format_arg(); + + test_single_format_arg(42); + if constexpr (is_same_v) { + test_single_format_arg(42); + } else { + test_single_format_arg(42); + } + test_single_format_arg(42); + test_single_format_arg(42); +#ifdef __cpp_char8_t + test_single_format_arg(42); +#endif // __cpp_char8_t + test_single_format_arg(42); + test_single_format_arg(42); + + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + if constexpr (sizeof(int) == sizeof(ptrdiff_t)) { + test_single_format_arg(42); + } else { + test_single_format_arg(42); + } + + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + if constexpr (is_same_v) { + test_single_format_arg(42); + } else { + test_single_format_arg(42); + } + test_single_format_arg(42); + if constexpr (is_same_v) { + test_single_format_arg(42); + } else { + test_single_format_arg(42); + } + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + test_single_format_arg(42); + if constexpr (sizeof(unsigned int) == sizeof(size_t)) { + test_single_format_arg(42); + } else { + test_single_format_arg(42); + } + + test_single_format_arg(42.f); + test_single_format_arg(42.); + test_single_format_arg(42.); + + test_single_format_arg(nullptr); + + test_single_format_arg(get_input_literal()); + test_single_format_arg, Arg_type::string_type>(get_input_sv()); +} + +int main() { + test_basic_format_arg(); + test_basic_format_arg(); + test_format_arg_store(); + test_format_arg_store(); +}