diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst index 311a06e34c49d..14e1e09907fad 100644 --- a/libcxx/docs/ReleaseNotes/18.rst +++ b/libcxx/docs/ReleaseNotes/18.rst @@ -61,6 +61,7 @@ Implemented Papers - P1759R6 - Native handles and file streams - P2868R3 - Remove Deprecated ``std::allocator`` Typedef From C++26 - P2517R1 - Add a conditional ``noexcept`` specification to ``std::apply`` +- P2637R3 - Member ``visit`` - P2447R6 - ``span`` over initializer list diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv index f6124d9a6c4e3..e49290b45589a 100644 --- a/libcxx/docs/Status/Cxx2cPapers.csv +++ b/libcxx/docs/Status/Cxx2cPapers.csv @@ -17,7 +17,7 @@ "`P0792R14 `__","LWG","``function_ref``: a type-erased callable reference","Varna June 2023","","","" "`P2874R2 `__","LWG","Mandating Annex D Require No More","Varna June 2023","","","" "`P2757R3 `__","LWG","Type-checking format args","Varna June 2023","","","|format|" -"`P2637R3 `__","LWG","Member ``visit``","Varna June 2023","|Partial|","18.0","" +"`P2637R3 `__","LWG","Member ``visit``","Varna June 2023","|Complete|","18.0","" "`P2641R4 `__","CWG, LWG","Checking if a ``union`` alternative is active","Varna June 2023","","","" "`P1759R6 `__","LWG","Native handles and file streams","Varna June 2023","|Complete|","18.0","" "`P2697R1 `__","LWG","Interfacing ``bitset`` with ``string_view``","Varna June 2023","|Complete|","18.0","" diff --git a/libcxx/docs/Status/FormatIssues.csv b/libcxx/docs/Status/FormatIssues.csv index 513988d08036c..6e58e752191ea 100644 --- a/libcxx/docs/Status/FormatIssues.csv +++ b/libcxx/docs/Status/FormatIssues.csv @@ -16,7 +16,7 @@ Number,Name,Standard,Assignee,Status,First released version "`P2693R1 `__","Formatting ``thread::id`` and ``stacktrace``","C++23","Mark de Wever","|In Progress|" "`P2510R3 `__","Formatting pointers","C++26","Mark de Wever","|Complete|",17.0 "`P2757R3 `__","Type-checking format args","C++26","","", -"`P2637R3 `__","Member ``visit``","C++26","","", +"`P2637R3 `__","Member ``visit``","C++26","Hristo Hristov","|Complete|",18.0 "`P2905R2 `__","Runtime format strings","C++26 DR","Mark de Wever","|Complete|",18.0 "`P2918R2 `__","Runtime format strings II","C++26","Mark de Wever","|Complete|",18.0 "`P2909R4 `__","Fix formatting of code units as integers (Dude, where’s my ``char``?)","C++26 DR","Mark de Wever","|Complete|",18.0 diff --git a/libcxx/include/__config b/libcxx/include/__config index 2825d03b7c205..639f88e21d274 100644 --- a/libcxx/include/__config +++ b/libcxx/include/__config @@ -984,6 +984,12 @@ typedef __char32_t char32_t; # define _LIBCPP_DEPRECATED_IN_CXX23 # endif +# if _LIBCPP_STD_VER >= 26 +# define _LIBCPP_DEPRECATED_IN_CXX26 _LIBCPP_DEPRECATED +# else +# define _LIBCPP_DEPRECATED_IN_CXX26 +# endif + # if !defined(_LIBCPP_HAS_NO_CHAR8_T) # define _LIBCPP_DEPRECATED_WITH_CHAR8_T _LIBCPP_DEPRECATED # else diff --git a/libcxx/include/__format/format_arg.h b/libcxx/include/__format/format_arg.h index 10fca15d5a7a9..02ee3cef7d402 100644 --- a/libcxx/include/__format/format_arg.h +++ b/libcxx/include/__format/format_arg.h @@ -93,7 +93,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr __arg_t __get_packed_type(uint64_t __types, size } // namespace __format -// This function is not user obervable, so it can directly use the non-standard +// This function is not user observable, so it can directly use the non-standard // types of the "variant". See __arg_t for more details. template _LIBCPP_HIDE_FROM_ABI decltype(auto) __visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg) { @@ -144,6 +144,59 @@ _LIBCPP_HIDE_FROM_ABI decltype(auto) __visit_format_arg(_Visitor&& __vis, basic_ __libcpp_unreachable(); } +# if _LIBCPP_STD_VER >= 26 && defined(_LIBCPP_HAS_EXPLICIT_THIS_PARAMETER) + +template +_LIBCPP_HIDE_FROM_ABI _Rp __visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg) { + switch (__arg.__type_) { + case __format::__arg_t::__none: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__monostate_); + case __format::__arg_t::__boolean: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__boolean_); + case __format::__arg_t::__char_type: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__char_type_); + case __format::__arg_t::__int: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__int_); + case __format::__arg_t::__long_long: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__long_long_); + case __format::__arg_t::__i128: +# ifndef _LIBCPP_HAS_NO_INT128 + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__i128_); +# else + __libcpp_unreachable(); +# endif + case __format::__arg_t::__unsigned: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__unsigned_); + case __format::__arg_t::__unsigned_long_long: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__unsigned_long_long_); + case __format::__arg_t::__u128: +# ifndef _LIBCPP_HAS_NO_INT128 + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__u128_); +# else + __libcpp_unreachable(); +# endif + case __format::__arg_t::__float: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__float_); + case __format::__arg_t::__double: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__double_); + case __format::__arg_t::__long_double: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__long_double_); + case __format::__arg_t::__const_char_type_ptr: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__const_char_type_ptr_); + case __format::__arg_t::__string_view: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__string_view_); + case __format::__arg_t::__ptr: + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), __arg.__value_.__ptr_); + case __format::__arg_t::__handle: + return std::invoke_r<_Rp>( + std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__arg.__value_.__handle_}); + } + + __libcpp_unreachable(); +} + +# endif // _LIBCPP_STD_VER >= 26 && defined(_LIBCPP_HAS_EXPLICIT_THIS_PARAMETER) + /// Contains the values used in basic_format_arg. /// /// This is a separate type so it's possible to store the values and types in @@ -227,6 +280,52 @@ class _LIBCPP_TEMPLATE_VIS basic_format_arg { _LIBCPP_HIDE_FROM_ABI explicit operator bool() const noexcept { return __type_ != __format::__arg_t::__none; } +# if _LIBCPP_STD_VER >= 26 && defined(_LIBCPP_HAS_EXPLICIT_THIS_PARAMETER) + + // This function is user facing, so it must wrap the non-standard types of + // the "variant" in a handle to stay conforming. See __arg_t for more details. + template + _LIBCPP_HIDE_FROM_ABI decltype(auto) visit(this basic_format_arg __arg, _Visitor&& __vis) { + switch (__arg.__type_) { +# ifndef _LIBCPP_HAS_NO_INT128 + case __format::__arg_t::__i128: { + typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__i128_}; + return std::invoke(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h}); + } + + case __format::__arg_t::__u128: { + typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__u128_}; + return std::invoke(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h}); + } +# endif + default: + return std::__visit_format_arg(std::forward<_Visitor>(__vis), __arg); + } + } + + // This function is user facing, so it must wrap the non-standard types of + // the "variant" in a handle to stay conforming. See __arg_t for more details. + template + _LIBCPP_HIDE_FROM_ABI _Rp visit(this basic_format_arg __arg, _Visitor&& __vis) { + switch (__arg.__type_) { +# ifndef _LIBCPP_HAS_NO_INT128 + case __format::__arg_t::__i128: { + typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__i128_}; + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h}); + } + + case __format::__arg_t::__u128: { + typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__u128_}; + return std::invoke_r<_Rp>(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h}); + } +# endif + default: + return std::__visit_format_arg<_Rp>(std::forward<_Visitor>(__vis), __arg); + } + } + +# endif // _LIBCPP_STD_VER >= 26 && defined(_LIBCPP_HAS_EXPLICIT_THIS_PARAMETER) + private: using char_type = typename _Context::char_type; @@ -267,7 +366,11 @@ class _LIBCPP_TEMPLATE_VIS basic_format_arg<_Context>::handle { // This function is user facing, so it must wrap the non-standard types of // the "variant" in a handle to stay conforming. See __arg_t for more details. template -_LIBCPP_HIDE_FROM_ABI decltype(auto) visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg) { +# if _LIBCPP_STD_VER >= 26 && defined(_LIBCPP_HAS_EXPLICIT_THIS_PARAMETER) +_LIBCPP_DEPRECATED_IN_CXX26 +# endif + _LIBCPP_HIDE_FROM_ABI decltype(auto) + visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg) { switch (__arg.__type_) { # ifndef _LIBCPP_HAS_NO_INT128 case __format::__arg_t::__i128: { @@ -279,7 +382,7 @@ _LIBCPP_HIDE_FROM_ABI decltype(auto) visit_format_arg(_Visitor&& __vis, basic_fo typename __basic_format_arg_value<_Context>::__handle __h{__arg.__value_.__u128_}; return std::invoke(std::forward<_Visitor>(__vis), typename basic_format_arg<_Context>::handle{__h}); } -# endif +# endif // _LIBCPP_STD_VER >= 26 && defined(_LIBCPP_HAS_EXPLICIT_THIS_PARAMETER) default: return std::__visit_format_arg(std::forward<_Visitor>(__vis), __arg); } diff --git a/libcxx/include/__format/format_context.h b/libcxx/include/__format/format_context.h index 5b252b81f691b..0beaa84b028be 100644 --- a/libcxx/include/__format/format_context.h +++ b/libcxx/include/__format/format_context.h @@ -163,20 +163,25 @@ class _LIBCPP_TEMPLATE_VIS basic_format_context basic_format_arg { - if constexpr (same_as) - return {}; - else if constexpr (same_as::handle>) - // At the moment it's not possible for formatting to use a re-targeted handle. - // TODO FMT add this when support is needed. - std::__throw_format_error("Re-targeting handle not supported"); - else - return basic_format_arg{ - __format::__determine_arg_t(), - __basic_format_arg_value(__arg)}; - }, - static_cast<_Context*>(__c)->arg(__id)); + auto __visitor = [&](auto __arg) -> basic_format_arg { + if constexpr (same_as) + return {}; + else if constexpr (same_as::handle>) + // At the moment it's not possible for formatting to use a re-targeted handle. + // TODO FMT add this when support is needed. + std::__throw_format_error("Re-targeting handle not supported"); + else + return basic_format_arg{ + __format::__determine_arg_t(), + __basic_format_arg_value(__arg)}; + }; +# if _LIBCPP_STD_VER >= 26 && defined(_LIBCPP_HAS_EXPLICIT_THIS_PARAMETER) + return static_cast<_Context*>(__c)->arg(__id).visit(std::move(__visitor)); +# else + _LIBCPP_SUPPRESS_DEPRECATED_PUSH + return std::visit_format_arg(std::move(__visitor), static_cast<_Context*>(__c)->arg(__id)); + _LIBCPP_SUPPRESS_DEPRECATED_POP +# endif // _LIBCPP_STD_VER >= 26 && defined(_LIBCPP_HAS_EXPLICIT_THIS_PARAMETER) }) { } diff --git a/libcxx/include/format b/libcxx/include/format index ab9b336d0cdab..64f6ba1d25284 100644 --- a/libcxx/include/format +++ b/libcxx/include/format @@ -170,7 +170,7 @@ namespace std { template class basic_format_arg; template - see below visit_format_arg(Visitor&& vis, basic_format_arg arg); + see below visit_format_arg(Visitor&& vis, basic_format_arg arg); // Deprecated in C++26 // [format.arg.store], class template format-arg-store template struct format-arg-store; // exposition only diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg/visit.pass.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg/visit.pass.cpp new file mode 100644 index 0000000000000..994ccc70a38da --- /dev/null +++ b/libcxx/test/std/utilities/format/format.arguments/format.arg/visit.pass.cpp @@ -0,0 +1,333 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23 +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME +// The tested functionality needs deducing this. +// UNSUPPORTED: clang-16 || clang-17 +// XFAIL: apple-clang + +// + +// class basic_format_arg; + +// template +// decltype(auto) visit(this basic_format_arg arg, Visitor&& vis); // since C++26 + +#include +#include +#include +#include + +#include "constexpr_char_traits.h" +#include "make_string.h" +#include "min_allocator.h" +#include "test_macros.h" + +template +void test(From value) { + auto store = std::make_format_args(value); + std::basic_format_args format_args{store}; + + LIBCPP_ASSERT(format_args.__size() == 1); + assert(format_args.get(0)); + + auto result = format_args.get(0).visit([v = To(value)](auto a) -> To { + if constexpr (std::is_same_v) { + assert(v == a); + return a; + } else { + assert(false); + return {}; + } + }); + + using ct = std::common_type_t; + assert(static_cast(result) == static_cast(value)); +} + +// Some types, as an extension, are stored in the variant. The Standard +// requires them to be observed as a handle. +template +void test_handle(T value) { + auto store = std::make_format_args(value); + std::basic_format_args format_args{store}; + + LIBCPP_ASSERT(format_args.__size() == 1); + assert(format_args.get(0)); + + format_args.get(0).visit([](auto a) { + assert((std::is_same_v::handle>)); + }); +} + +// Test specific for string and string_view. +// +// Since both result in a string_view there's no need to pass this as a +// template argument. +template +void test_string_view(From value) { + auto store = std::make_format_args(value); + std::basic_format_args format_args{store}; + + LIBCPP_ASSERT(format_args.__size() == 1); + assert(format_args.get(0)); + + using CharT = typename Context::char_type; + using To = std::basic_string_view; + using V = std::basic_string; + + auto result = format_args.get(0).visit([v = V(value.begin(), value.end())](auto a) -> To { + if constexpr (std::is_same_v) { + assert(v == a); + return a; + } else { + assert(false); + return {}; + } + }); + + assert(std::equal(value.begin(), value.end(), result.begin(), result.end())); +} + +template +void test() { + using Context = std::basic_format_context; + std::basic_string empty; + std::basic_string str = MAKE_STRING(CharT, "abc"); + + // Test boolean types. + + test(true); + test(false); + + // Test CharT types. + + test('a'); + test('z'); + test('0'); + test('9'); + + // Test char types. + + if (std::is_same_v) { + // char to char -> char + test('a'); + test('z'); + test('0'); + test('9'); + } else { + if (std::is_same_v) { + // char to wchar_t -> wchar_t + test('a'); + test('z'); + test('0'); + test('9'); + } else if (std::is_signed_v) { + // char to CharT -> int + // This happens when CharT is a char8_t, char16_t, or char32_t and char + // is a signed type. + // Note if sizeof(CharT) > sizeof(int) this test fails. If there are + // platforms where that occurs extra tests need to be added for char32_t + // testing it against a long long. + test('a'); + test('z'); + test('0'); + test('9'); + } else { + // char to CharT -> unsigned + // This happens when CharT is a char8_t, char16_t, or char32_t and char + // is an unsigned type. + // Note if sizeof(CharT) > sizeof(unsigned) this test fails. If there are + // platforms where that occurs extra tests need to be added for char32_t + // testing it against an unsigned long long. + test('a'); + test('z'); + test('0'); + test('9'); + } + } + + // Test signed integer types. + + test(std::numeric_limits::min()); + test(0); + test(std::numeric_limits::max()); + + test(std::numeric_limits::min()); + test(std::numeric_limits::min()); + test(0); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + + test(std::numeric_limits::min()); + test(std::numeric_limits::min()); + test(std::numeric_limits::min()); + test(0); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + + using LongToType = std::conditional_t; + + test(std::numeric_limits::min()); + test(std::numeric_limits::min()); + test(std::numeric_limits::min()); + test(std::numeric_limits::min()); + test(0); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + + test(std::numeric_limits::min()); + test(std::numeric_limits::min()); + test(std::numeric_limits::min()); + test(std::numeric_limits::min()); + test(std::numeric_limits::min()); + test(0); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + +#ifndef TEST_HAS_NO_INT128 + test_handle(0); +#endif // TEST_HAS_NO_INT128 + + // Test unsigned integer types. + + test(0); + test(std::numeric_limits::max()); + + test(0); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + + test(0); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + + using UnsignedLongToType = + std::conditional_t; + + test(0); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + + test(0); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + test(std::numeric_limits::max()); + +#ifndef TEST_HAS_NO_INT128 + test_handle(0); +#endif // TEST_HAS_NO_INT128 + + // Test floating point types. + + test(-std::numeric_limits::max()); + test(-std::numeric_limits::min()); + test(-0.0); + test(0.0); + test(std::numeric_limits::min()); + test(std::numeric_limits::max()); + + test(-std::numeric_limits::max()); + test(-std::numeric_limits::min()); + test(-0.0); + test(0.0); + test(std::numeric_limits::min()); + test(std::numeric_limits::max()); + + test(-std::numeric_limits::max()); + test(-std::numeric_limits::min()); + test(-0.0); + test(0.0); + test(std::numeric_limits::min()); + test(std::numeric_limits::max()); + + // Test const CharT pointer types. + + test(empty.c_str()); + test(str.c_str()); + + // Test string_view types. + + { + using From = std::basic_string_view; + + test_string_view(From()); + test_string_view(From(empty.c_str())); + test_string_view(From(str.c_str())); + } + + { + using From = std::basic_string_view>; + + test_string_view(From()); + test_string_view(From(empty.c_str())); + test_string_view(From(str.c_str())); + } + + // Test string types. + + { + using From = std::basic_string; + + test_string_view(From()); + test_string_view(From(empty.c_str())); + test_string_view(From(str.c_str())); + } + + { + using From = std::basic_string, std::allocator>; + + test_string_view(From()); + test_string_view(From(empty.c_str())); + test_string_view(From(str.c_str())); + } + + { + using From = std::basic_string, min_allocator>; + + test_string_view(From()); + test_string_view(From(empty.c_str())); + test_string_view(From(str.c_str())); + } + + { + using From = std::basic_string, min_allocator>; + + test_string_view(From()); + test_string_view(From(empty.c_str())); + test_string_view(From(str.c_str())); + } + + // Test pointer types. + + test(nullptr); + int i = 0; + test(static_cast(&i)); + const int ci = 0; + test(static_cast(&ci)); +} + +int main(int, char**) { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg/visit.return_type.pass.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg/visit.return_type.pass.cpp new file mode 100644 index 0000000000000..473278b7b4434 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.arguments/format.arg/visit.return_type.pass.cpp @@ -0,0 +1,369 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23 +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME +// The tested functionality needs deducing this. +// UNSUPPORTED: clang-16 || clang-17 +// XFAIL: apple-clang + +// + +// class basic_format_arg; + +// template +// R visit(this basic_format_arg arg, Visitor&& vis); + +#include +#include +#include +#include + +#include "constexpr_char_traits.h" +#include "make_string.h" +#include "min_allocator.h" +#include "test_macros.h" + +// The expected result type shouldn't matter,therefore use a hardcoded value for simplicity. +using ExpectedResultType = bool; +constexpr ExpectedResultType visited{true}; + +template +ExpectedR make_expected_result() { + if constexpr (std::is_same_v) { + return true; + } else if constexpr (std::is_same_v) { + return 192812079084L; + } else { + return "visited"; + } +} + +template +void test(From value, const ExpectedR& expectedValue) { + auto store = std::make_format_args(value); + std::basic_format_args format_args{store}; + + LIBCPP_ASSERT(format_args.__size() == 1); + assert(format_args.get(0)); + + // member + { + std::same_as decltype(auto) result = + format_args.get(0).template visit([v = To(value)](auto a) -> ExpectedR { + if constexpr (std::is_same_v) { + assert(v == a); + return make_expected_result(); + } else { + assert(false); + return {}; + } + }); + + assert(result == expectedValue); + } +} + +// Some types, as an extension, are stored in the variant. The Standard +// requires them to be observed as a handle. +template +void test_handle(T value, ExpectedR expectedValue) { + auto store = std::make_format_args(value); + std::basic_format_args format_args{store}; + + LIBCPP_ASSERT(format_args.__size() == 1); + assert(format_args.get(0)); + + std::same_as decltype(auto) result = format_args.get(0).template visit([](auto a) -> ExpectedR { + assert((std::is_same_v::handle>)); + + return make_expected_result(); + }); + + assert(result == expectedValue); +} + +// Test specific for string and string_view. +// +// Since both result in a string_view there's no need to pass this as a +// template argument. +template +void test_string_view(From value, ExpectedR expectedValue) { + auto store = std::make_format_args(value); + std::basic_format_args format_args{store}; + + LIBCPP_ASSERT(format_args.__size() == 1); + assert(format_args.get(0)); + + using CharT = typename Context::char_type; + using To = std::basic_string_view; + using V = std::basic_string; + + std::same_as decltype(auto) result = + format_args.get(0).template visit([v = V(value.begin(), value.end())](auto a) -> ExpectedR { + if constexpr (std::is_same_v) { + assert(v == a); + return make_expected_result(); + } else { + assert(false); + return {}; + } + }); + + assert(result == expectedValue); +} + +template +void test() { + using Context = std::basic_format_context; + std::basic_string empty; + std::basic_string str = MAKE_STRING(CharT, "abc"); + + // Test boolean types. + + test(true, visited); + test(false, visited); + + test(true, "visited"); + test(false, "visited"); + + test(true, 192812079084L); + test(false, 192812079084L); + + // Test CharT types. + + test('a', visited); + test('z', visited); + test('0', visited); + test('9', visited); + + // Test char types. + + if (std::is_same_v) { + // char to char -> char + test('a', visited); + test('z', visited); + test('0', visited); + test('9', visited); + } else { + if (std::is_same_v) { + // char to wchar_t -> wchar_t + test('a', visited); + test('z', visited); + test('0', visited); + test('9', visited); + } else if (std::is_signed_v) { + // char to CharT -> int + // This happens when CharT is a char8_t, char16_t, or char32_t and char + // is a signed type. + // Note if sizeof(CharT) > sizeof(int) this test fails. If there are + // platforms where that occurs extra tests need to be added for char32_t + // testing it against a long long. + test('a', visited); + test('z', visited); + test('0', visited); + test('9', visited); + } else { + // char to CharT -> unsigned + // This happens when CharT is a char8_t, char16_t, or char32_t and char + // is an unsigned type. + // Note if sizeof(CharT) > sizeof(unsigned) this test fails. If there are + // platforms where that occurs extra tests need to be added for char32_t + // testing it against an unsigned long long. + test('a', visited); + test('z', visited); + test('0', visited); + test('9', visited); + } + } + + // Test signed integer types. + + test(std::numeric_limits::min(), visited); + test(0, visited); + test(std::numeric_limits::max(), visited); + + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::min(), visited); + test(0, visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::min(), visited); + test(0, visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + + using LongToType = std::conditional_t; + + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::min(), visited); + test(0, visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::min(), visited); + test(0, visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + +#ifndef TEST_HAS_NO_INT128 + test_handle(0, visited); +#endif // TEST_HAS_NO_INT128 + + // Test unsigned integer types. + + test(0, visited); + test(std::numeric_limits::max(), visited); + + test(0, visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + + test(0, visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + + using UnsignedLongToType = + std::conditional_t; + + test(0, visited); + test( + std::numeric_limits::max(), visited); + test( + std::numeric_limits::max(), visited); + test(std::numeric_limits::max(), visited); + test( + std::numeric_limits::max(), visited); + + test(0, visited); + test( + std::numeric_limits::max(), visited); + test( + std::numeric_limits::max(), visited); + test( + std::numeric_limits::max(), visited); + test( + std::numeric_limits::max(), visited); + test( + std::numeric_limits::max(), visited); + +#ifndef TEST_HAS_NO_INT128 + test_handle(0, visited); +#endif // TEST_HAS_NO_INT128 + + // Test floating point types. + + test(-std::numeric_limits::max(), visited); + test(-std::numeric_limits::min(), visited); + test(-0.0, visited); + test(0.0, visited); + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::max(), visited); + + test(-std::numeric_limits::max(), visited); + test(-std::numeric_limits::min(), visited); + test(-0.0, visited); + test(0.0, visited); + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::max(), visited); + + test(-std::numeric_limits::max(), visited); + test(-std::numeric_limits::min(), visited); + test(-0.0, visited); + test(0.0, visited); + test(std::numeric_limits::min(), visited); + test(std::numeric_limits::max(), visited); + + // Test const CharT pointer types. + + test(empty.c_str(), visited); + test(str.c_str(), visited); + + // Test string_view types. + + { + using From = std::basic_string_view; + + test_string_view(From(), visited); + test_string_view(From(empty.c_str()), visited); + test_string_view(From(str.c_str()), visited); + } + { + using From = std::basic_string_view>; + + test_string_view(From(), visited); + test_string_view(From(empty.c_str()), visited); + test_string_view(From(str.c_str()), visited); + } + + // Test string types. + + { + using From = std::basic_string; + + test_string_view(From(), visited); + test_string_view(From(empty.c_str()), visited); + test_string_view(From(str.c_str()), visited); + } + + { + using From = std::basic_string, std::allocator>; + + test_string_view(From(), visited); + test_string_view(From(empty.c_str()), visited); + test_string_view(From(str.c_str()), visited); + } + + { + using From = std::basic_string, min_allocator>; + + test_string_view(From(), visited); + test_string_view(From(empty.c_str()), visited); + test_string_view(From(str.c_str()), visited); + } + + { + using From = std::basic_string, min_allocator>; + + test_string_view(From(), visited); + test_string_view(From(empty.c_str()), visited); + test_string_view(From(str.c_str()), visited); + } + + // Test pointer types. + + test(nullptr, visited); + int i = 0; + test(static_cast(&i), visited); + const int ci = 0; + test(static_cast(&ci), visited); +} + +int main(int, char**) { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg/visit_format_arg.deprecated.verify.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg/visit_format_arg.deprecated.verify.cpp new file mode 100644 index 0000000000000..acd9228369e60 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.arguments/format.arg/visit_format_arg.deprecated.verify.cpp @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23 +// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME +// UNSUPPORTED: clang-16 || clang-17 +// XFAIL: apple-clang + +// + +// template +// see below visit_format_arg(Visitor&& vis, basic_format_arg arg); + +#include +#include + +#include "test_macros.h" + +template +void test(From value) { + using Context = std::basic_format_context; + auto store = std::make_format_args(value); + std::basic_format_args format_args{store}; + + // expected-warning-re@+1 1-2 {{std::basic_format_context{{.*}}' is deprecated}} + std::ignore = std::visit_format_arg([]([[maybe_unused]] auto a) -> To { return {}; }, format_args.get(0)); +} + +void test() { + test('a'); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test('a'); +#endif +} diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp index 3ddf2d0ff732a..3497d8935c8d6 100644 --- a/libcxx/test/std/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp +++ b/libcxx/test/std/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp @@ -11,7 +11,7 @@ // // template -// see below visit_format_arg(Visitor&& vis, basic_format_arg arg); +// see below visit_format_arg(Visitor&& vis, basic_format_arg arg); // Deprecated in C++26 #include #include @@ -23,6 +23,10 @@ #include "make_string.h" #include "min_allocator.h" +#if TEST_STD_VER >= 26 && defined(TEST_HAS_EXPLICIT_THIS_PARAMETER) +TEST_CLANG_DIAGNOSTIC_IGNORED("-Wdeprecated-declarations") +#endif + template void test(From value) { auto store = std::make_format_args(value); diff --git a/libcxx/test/std/utilities/format/format.arguments/format.args/get.pass.cpp b/libcxx/test/std/utilities/format/format.arguments/format.args/get.pass.cpp index 0f3931adf54df..a5d3703397f4e 100644 --- a/libcxx/test/std/utilities/format/format.arguments/format.args/get.pass.cpp +++ b/libcxx/test/std/utilities/format/format.arguments/format.args/get.pass.cpp @@ -24,14 +24,17 @@ void test(From value) { auto store = std::make_format_args(value); const std::basic_format_args format_args{store}; - std::visit_format_arg( - [v = To(value)](auto a) { - if constexpr (std::is_same_v) - assert(v == a); - else - assert(false); - }, - format_args.get(0)); + auto visitor = [v = To(value)](auto a) { + if constexpr (std::is_same_v) + assert(v == a); + else + assert(false); + }; +#if TEST_STD_VER >= 26 && defined(TEST_HAS_EXPLICIT_THIS_PARAMETER) + format_args.get(0).visit(visitor); +#else + std::visit_format_arg(visitor, format_args.get(0)); +#endif } // Some types, as an extension, are stored in the variant. The Standard @@ -41,9 +44,12 @@ void test_handle(T value) { auto store = std::make_format_args(value); std::basic_format_args format_args{store}; - std::visit_format_arg( - [](auto a) { assert((std::is_same_v::handle>)); }, - format_args.get(0)); + auto visitor = [](auto a) { assert((std::is_same_v::handle>)); }; +#if TEST_STD_VER >= 26 && defined(TEST_HAS_EXPLICIT_THIS_PARAMETER) + format_args.get(0).visit(visitor); +#else + std::visit_format_arg(visitor, format_args.get(0)); +#endif } // Test specific for string and string_view. @@ -58,14 +64,18 @@ void test_string_view(From value) { using CharT = typename Context::char_type; using To = std::basic_string_view; using V = std::basic_string; - std::visit_format_arg( - [v = V(value.begin(), value.end())](auto a) { - if constexpr (std::is_same_v) - assert(v == a); - else - assert(false); - }, - format_args.get(0)); + + auto visitor = [v = V(value.begin(), value.end())](auto a) { + if constexpr (std::is_same_v) + assert(v == a); + else + assert(false); + }; +#if TEST_STD_VER >= 26 && defined(TEST_HAS_EXPLICIT_THIS_PARAMETER) + format_args.get(0).visit(visitor); +#else + std::visit_format_arg(visitor, format_args.get(0)); +#endif } template diff --git a/libcxx/test/support/test_basic_format_arg.h b/libcxx/test/support/test_basic_format_arg.h index d8547d34b6db4..1ec719a48b6c8 100644 --- a/libcxx/test/support/test_basic_format_arg.h +++ b/libcxx/test/support/test_basic_format_arg.h @@ -7,18 +7,22 @@ #include #include +#include #include "test_macros.h" /// Returns whether the basic_format_arg contains a type T with the expected value. template bool test_basic_format_arg(std::basic_format_arg arg, T expected) { - return std::visit_format_arg( - [expected](auto a) { - if constexpr (std::same_as) - return a == expected; - else - return false; - }, - arg); + auto visitor = [expected](auto a) { + if constexpr (std::same_as) + return a == expected; + else + return false; + }; +#if TEST_STD_VER >= 26 && defined(TEST_HAS_EXPLICIT_THIS_PARAMETER) + return arg.visit(std::move(visitor)); +#else + return std::visit_format_arg(std::move(visitor), arg); +#endif } diff --git a/libcxx/test/support/test_macros.h b/libcxx/test/support/test_macros.h index fe68e13de6bcb..24f69c758f365 100644 --- a/libcxx/test/support/test_macros.h +++ b/libcxx/test/support/test_macros.h @@ -470,4 +470,9 @@ inline Tp const& DoNotOptimize(Tp const& value) { # define TEST_IF_AIX(arg_true, arg_false) arg_false #endif +// Clang-18 has support for deducing this, but it does not set the FTM. +#ifdef _LIBCPP_HAS_EXPLICIT_THIS_PARAMETER +# define TEST_HAS_EXPLICIT_THIS_PARAMETER +#endif + #endif // SUPPORT_TEST_MACROS_HPP diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index a5df187b046f6..667dd75278c8b 100755 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -467,6 +467,7 @@ def add_version_header(tc): # "c++20": 202110 Not implemented P2372R3 Fixing locale handling in chrono formatters "c++20": 202106, # "c++23": 202207, Not implemented P2419R2 Clarify handling of encodings in localized formatting of chrono types + # "c++26": 202306, P2637R3 Member Visit (implemented) # "c++26": 202311, P2918R2 Runtime format strings II (implemented) }, # Note these three papers are adopted at the June 2023 meeting and have sequential numbering