Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc++][strings] P2591R5: Concatenation of strings and string views #88389

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2d606f4
[libc++][strings] P2591R5: Concatenation of strings and string views
H-G-Hristov Apr 11, 2024
af56ab9
Implemented tests
H-G-Hristov Apr 11, 2024
474d5b9
Cleanup
H-G-Hristov Apr 11, 2024
38f6e85
Restored formatting
H-G-Hristov Apr 11, 2024
ea270b6
Addressed review comments
H-G-Hristov Apr 13, 2024
292cd8f
Removed `constexpr_char_traits` tests
H-G-Hristov Apr 13, 2024
f312052
Merge branch 'main' into hgh/libcxx/P2591R5-Concatenation-of-string-a…
H-G-Hristov Apr 13, 2024
9a86ea1
Use Will Hawkins implementation
H-G-Hristov Apr 15, 2024
695b4b6
Cleanup
H-G-Hristov Apr 15, 2024
2df55a5
Merge branch 'main' into hgh/libcxx/P2591R5-Concatenation-of-string-a…
H-G-Hristov Apr 15, 2024
6b77e5a
Reordered forward declared functions
H-G-Hristov Apr 16, 2024
92d08d6
Merge branch 'main' into hgh/libcxx/P2591R5-Concatenation-of-string-a…
H-G-Hristov May 9, 2024
694e6fd
Fixed rvalue argument overload
H-G-Hristov May 9, 2024
be56164
Workaround for `constexpr_char_traits` not compatible with literals
H-G-Hristov May 10, 2024
20b9120
Minor cleanup
H-G-Hristov May 10, 2024
a8c28fb
Fixed formatting
H-G-Hristov May 10, 2024
16fcbba
Merge branch 'main' into hgh/libcxx/P2591R5-Concatenation-of-string-a…
H-G-Hristov May 10, 2024
bdbaf38
Cleanup
H-G-Hristov May 10, 2024
5d804f5
Merge branch 'main' into hgh/libcxx/P2591R5-Concatenation-of-string-a…
H-G-Hristov May 10, 2024
6e4d6b4
Merge branch 'hgh/libcxx/P2591R5-Concatenation-of-string-and-string-v…
H-G-Hristov May 10, 2024
9f27ece
Try to fix ASAN tests
H-G-Hristov May 10, 2024
43c78be
Merge branch 'main' into hgh/libcxx/P2591R5-Concatenation-of-string-a…
Zingam May 10, 2024
d56da5f
Add a workaround note
H-G-Hristov May 16, 2024
f4f26df
Merge branch 'main' into hgh/libcxx/P2591R5-Concatenation-of-string-a…
H-G-Hristov Jul 10, 2024
da99993
Fixed formatting
H-G-Hristov Jul 10, 2024
907bcc4
Merge branch 'main' into hgh/libcxx/P2591R5-Concatenation-of-string-a…
Zingam Jul 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions libcxx/docs/FeatureTestMacroTable.rst
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,8 @@ Status
---------------------------------------------------------- -----------------
``__cpp_lib_sstream_from_string_view`` ``202306L``
---------------------------------------------------------- -----------------
``__cpp_lib_string_view`` ``202403L``
---------------------------------------------------------- -----------------
``__cpp_lib_submdspan`` *unimplemented*
---------------------------------------------------------- -----------------
``__cpp_lib_text_encoding`` *unimplemented*
Expand Down
1 change: 1 addition & 0 deletions libcxx/docs/ReleaseNotes/19.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Implemented Papers
- P2872R3 - Remove ``wstring_convert`` From C++26
- P3142R0 - Printing Blank Lines with ``println`` (as DR against C++23)
- P2944R3 - Comparisons for ``reference_wrapper`` (comparison operators for ``reference_wrapper`` only)
- P2591R5 - Concatenation of strings and string views
- P2302R4 - ``std::ranges::contains``
- P1659R3 - ``std::ranges::starts_with`` and ``std::ranges::ends_with``
- P3029R1 - Better ``mdspan``'s CTAD
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx2cPapers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"`P2845R8 <https://wg21.link/P2845R8>`__","LWG","Formatting of ``std::filesystem::path``","Tokyo March 2024","","","|format|"
"`P0493R5 <https://wg21.link/P0493R5>`__","LWG","Atomic minimum/maximum","Tokyo March 2024","","",""
"`P2542R8 <https://wg21.link/P2542R8>`__","LWG","``views::concat``","Tokyo March 2024","","","|ranges|"
"`P2591R5 <https://wg21.link/P2591R5>`__","LWG","Concatenation of strings and string views","Tokyo March 2024","","",""
"`P2591R5 <https://wg21.link/P2591R5>`__","LWG","Concatenation of strings and string views","Tokyo March 2024","|Complete|","19.0",""
"`P2248R8 <https://wg21.link/P2248R8>`__","LWG","Enabling list-initialization for algorithms","Tokyo March 2024","","",""
"`P2810R4 <https://wg21.link/P2810R4>`__","LWG","``is_debugger_present`` ``is_replaceable``","Tokyo March 2024","","",""
"`P1068R11 <https://wg21.link/P1068R11>`__","LWG","Vector API for random number generation","Tokyo March 2024","","",""
Expand Down
90 changes: 90 additions & 0 deletions libcxx/include/string
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,24 @@ template<class charT, class traits, class Allocator>
basic_string<charT, traits, Allocator>
operator+(const basic_string<charT, traits, Allocator>& lhs, charT rhs); // constexpr since C++20

template<class charT, class traits, class Allocator>
constexpr basic_string<charT, traits, Allocator>
operator+(const basic_string<charT, traits, Allocator>& lhs,
type_identity_t<basic_string_view<charT, traits>> rhs); // Since C++26
template<class charT, class traits, class Allocator>
constexpr basic_string<charT, traits, Allocator>
operator+(basic_string<charT, traits, Allocator>&& lhs,
type_identity_t<basic_string_view<charT, traits>> rhs); // Since C++26
template<class charT, class traits, class Allocator>
constexpr basic_string<charT, traits, Allocator>
operator+(type_identity_t<basic_string_view<charT, traits>> lhs,
const basic_string<charT, traits, Allocator>& rhs); // Since C++26
template<class charT, class traits, class Allocator>
constexpr basic_string<charT, traits, Allocator>
operator+(type_identity_t<basic_string_view<charT, traits>> lhs,
basic_string<charT, traits, Allocator>&& rhs); // Since C++26


template<class charT, class traits, class Allocator>
bool operator==(const basic_string<charT, traits, Allocator>& lhs,
const basic_string<charT, traits, Allocator>& rhs) noexcept; // constexpr since C++20
Expand Down Expand Up @@ -687,6 +705,20 @@ template <class _CharT, class _Traits, class _Allocator>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string<_CharT, _Traits, _Allocator>
operator+(const basic_string<_CharT, _Traits, _Allocator>& __x, _CharT __y);

#if _LIBCPP_STD_VER >= 26

template <class _CharT, class _Traits, class _Allocator>
_LIBCPP_HIDE_FROM_ABI constexpr basic_string<_CharT, _Traits, _Allocator>
operator+(const basic_string<_CharT, _Traits, _Allocator>& __lhs,
type_identity_t<basic_string_view<_CharT, _Traits>> __rhs);

template <class _CharT, class _Traits, class _Allocator>
_LIBCPP_HIDE_FROM_ABI constexpr basic_string<_CharT, _Traits, _Allocator>
operator+(type_identity_t<basic_string_view<_CharT, _Traits>> __lhs,
const basic_string<_CharT, _Traits, _Allocator>& __rhs);

#endif

extern template _LIBCPP_EXPORTED_FROM_ABI string operator+
<char, char_traits<char>, allocator<char> >(char const*, string const&);

Expand Down Expand Up @@ -2166,6 +2198,10 @@ private:
friend _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string operator+ <>(value_type, const basic_string&);
friend _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string operator+ <>(const basic_string&, const value_type*);
friend _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string operator+ <>(const basic_string&, value_type);
#if _LIBCPP_STD_VER >= 26
friend constexpr basic_string operator+ <>(const basic_string&, type_identity_t<__self_view>);
friend constexpr basic_string operator+ <>(type_identity_t<__self_view>, const basic_string&);
#endif
};

// These declarations must appear before any functions are implicitly used
Expand Down Expand Up @@ -4031,6 +4067,60 @@ operator+(basic_string<_CharT, _Traits, _Allocator>&& __lhs, _CharT __rhs) {

#endif // _LIBCPP_CXX03_LANG

#if _LIBCPP_STD_VER >= 26

template <class _CharT, class _Traits, class _Allocator>
_LIBCPP_HIDE_FROM_ABI constexpr basic_string<_CharT, _Traits, _Allocator>
operator+(const basic_string<_CharT, _Traits, _Allocator>& __lhs,
type_identity_t<basic_string_view<_CharT, _Traits>> __rhs) {
using _String = basic_string<_CharT, _Traits, _Allocator>;
typename _String::size_type __lhs_sz = __lhs.size();
typename _String::size_type __rhs_sz = __rhs.size();
_String __r(__uninitialized_size_tag(),
__lhs_sz + __rhs_sz,
_String::__alloc_traits::select_on_container_copy_construction(__lhs.get_allocator()));
auto __ptr = std::__to_address(__r.__get_pointer());
_Traits::copy(__ptr, __lhs.data(), __lhs_sz);
_Traits::copy(__ptr + __lhs_sz, __rhs.data(), __rhs_sz);
_Traits::assign(__ptr + __lhs_sz + __rhs_sz, 1, _CharT());
return __r;
}

template <class _CharT, class _Traits, class _Allocator>
_LIBCPP_HIDE_FROM_ABI constexpr basic_string<_CharT, _Traits, _Allocator>
operator+(basic_string<_CharT, _Traits, _Allocator>&& __lhs,
type_identity_t<basic_string_view<_CharT, _Traits>> __rhs) {
__lhs.append(__rhs);
return std::move(__lhs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure whether it would be better to drop std::move() here as __lhs is move-eligible here (ditto below).

Copy link
Contributor Author

@H-G-Hristov H-G-Hristov Apr 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the review. I reverted my interpretations to the original wording in the paper, so I'll keep this as is for now as I understand it this is our practice and also the older implementations uses the same style.

}

template <class _CharT, class _Traits, class _Allocator>
_LIBCPP_HIDE_FROM_ABI constexpr basic_string<_CharT, _Traits, _Allocator>
operator+(type_identity_t<basic_string_view<_CharT, _Traits>> __lhs,
const basic_string<_CharT, _Traits, _Allocator>& __rhs) {
using _String = basic_string<_CharT, _Traits, _Allocator>;
typename _String::size_type __lhs_sz = __lhs.size();
typename _String::size_type __rhs_sz = __rhs.size();
_String __r(__uninitialized_size_tag(),
__lhs_sz + __rhs_sz,
_String::__alloc_traits::select_on_container_copy_construction(__rhs.get_allocator()));
auto __ptr = std::__to_address(__r.__get_pointer());
_Traits::copy(__ptr, __lhs.data(), __lhs_sz);
_Traits::copy(__ptr + __lhs_sz, __rhs.data(), __rhs_sz);
_Traits::assign(__ptr + __lhs_sz + __rhs_sz, 1, _CharT());
return __r;
}

template <class _CharT, class _Traits, class _Allocator>
_LIBCPP_HIDE_FROM_ABI constexpr basic_string<_CharT, _Traits, _Allocator>
operator+(type_identity_t<basic_string_view<_CharT, _Traits>> __lhs,
basic_string<_CharT, _Traits, _Allocator>&& __rhs) {
__rhs.insert(0, __lhs);
return std::move(__rhs);
}

#endif // _LIBCPP_STD_VER >= 26

// swap

template <class _CharT, class _Traits, class _Allocator>
Expand Down
5 changes: 4 additions & 1 deletion libcxx/include/version
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ __cpp_lib_stdatomic_h 202011L <stdatomic.h>
__cpp_lib_string_contains 202011L <string> <string_view>
__cpp_lib_string_resize_and_overwrite 202110L <string>
__cpp_lib_string_udls 201304L <string>
__cpp_lib_string_view 201803L <string> <string_view>
__cpp_lib_string_view 202403L <string> <string_view>
201803L // C++20
201606L // C++17
__cpp_lib_submdspan 202306L <mdspan>
__cpp_lib_syncbuf 201803L <syncstream>
Expand Down Expand Up @@ -532,6 +533,8 @@ __cpp_lib_within_lifetime 202306L <type_traits>
# define __cpp_lib_span_at 202311L
# define __cpp_lib_span_initializer_list 202311L
# define __cpp_lib_sstream_from_string_view 202306L
# undef __cpp_lib_string_view
# define __cpp_lib_string_view 202403L
// # define __cpp_lib_submdspan 202306L
// # define __cpp_lib_text_encoding 202306L
# undef __cpp_lib_to_chars
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
__cpp_lib_string_udls 201304L [C++14]
__cpp_lib_string_view 201606L [C++17]
201803L [C++20]
202403L [C++26]
__cpp_lib_to_string 202306L [C++23]
*/

Expand Down Expand Up @@ -492,8 +493,8 @@
# ifndef __cpp_lib_string_view
# error "__cpp_lib_string_view should be defined in c++26"
# endif
# if __cpp_lib_string_view != 201803L
# error "__cpp_lib_string_view should have the value 201803L in c++26"
# if __cpp_lib_string_view != 202403L
# error "__cpp_lib_string_view should have the value 202403L in c++26"
# endif

# if !defined(_LIBCPP_VERSION)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
__cpp_lib_string_contains 202011L [C++23]
__cpp_lib_string_view 201606L [C++17]
201803L [C++20]
202403L [C++26]
*/

#include <string_view>
Expand Down Expand Up @@ -252,8 +253,8 @@
# ifndef __cpp_lib_string_view
# error "__cpp_lib_string_view should be defined in c++26"
# endif
# if __cpp_lib_string_view != 201803L
# error "__cpp_lib_string_view should have the value 201803L in c++26"
# if __cpp_lib_string_view != 202403L
# error "__cpp_lib_string_view should have the value 202403L in c++26"
# endif

#endif // TEST_STD_VER > 23
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@
__cpp_lib_string_udls 201304L [C++14]
__cpp_lib_string_view 201606L [C++17]
201803L [C++20]
202403L [C++26]
__cpp_lib_submdspan 202306L [C++26]
__cpp_lib_syncbuf 201803L [C++20]
__cpp_lib_text_encoding 202306L [C++26]
Expand Down Expand Up @@ -7711,8 +7712,8 @@
# ifndef __cpp_lib_string_view
# error "__cpp_lib_string_view should be defined in c++26"
# endif
# if __cpp_lib_string_view != 201803L
# error "__cpp_lib_string_view should have the value 201803L in c++26"
# if __cpp_lib_string_view != 202403L
# error "__cpp_lib_string_view should have the value 202403L in c++26"
# endif

# if !defined(_LIBCPP_VERSION)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//===----------------------------------------------------------------------===//
//
// 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

// <string>

// [string.op.plus]
//
// template<class charT, class traits, class Allocator>
// constexpr basic_string<charT, traits, Allocator>
// operator+(const basic_string<charT, traits, Allocator>& lhs,
// type_identity_t<basic_string_view<charT, traits>> rhs); // Since C++26
// template<class charT, class traits, class Allocator>
// constexpr basic_string<charT, traits, Allocator>
// operator+(basic_string<charT, traits, Allocator>&& lhs,
// type_identity_t<basic_string_view<charT, traits>> rhs); // Since C++26
// template<class charT, class traits, class Allocator>
// constexpr basic_string<charT, traits, Allocator>
// operator+(type_identity_t<basic_string_view<charT, traits>> lhs,
// const basic_string<charT, traits, Allocator>& rhs); // Since C++26
// template<class charT, class traits, class Allocator>
// constexpr basic_string<charT, traits, Allocator>
// operator+(type_identity_t<basic_string_view<charT, traits>> lhs,
// basic_string<charT, traits, Allocator>&& rhs); // Since C++26
Comment on lines +15 to +30
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Splitting the test for each overload will result in too much boilerplate. Should I do it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite often we do it separately. I see the other operator+ overloads of string also test more than one overload. So let's keep it.


#include <cassert>
#include <concepts>
#include <string>
#include <utility>

#include "asan_testing.h"
#include "constexpr_char_traits.h"
#include "make_string.h"
#include "min_allocator.h"
#include "test_allocator.h"
#include "test_macros.h"

#define CS(S) MAKE_CSTRING(CharT, S)
#define ST(S, a) std::basic_string<CharT, TraitsT, AllocT>(MAKE_CSTRING(CharT, S), MKSTR_LEN(CharT, S), a)
#define SV(S) std::basic_string_view<CharT, TraitsT>(MAKE_CSTRING(CharT, S), MKSTR_LEN(CharT, S))

template <typename CharT, typename TraitsT, typename AllocT>
constexpr void test(const CharT* x, const CharT* y, const CharT* expected) {
AllocT allocator;

// string& + string_view
{
std::basic_string<CharT, TraitsT, AllocT> st{x, allocator};
std::basic_string_view<CharT, TraitsT> sv{y};

std::same_as<std::basic_string<CharT, TraitsT, AllocT>> decltype(auto) result = st + sv;
assert(result == expected);
assert(result.get_allocator() == allocator);
LIBCPP_ASSERT(is_string_asan_correct(st + sv));
}
// const string& + string_view
{
const std::basic_string<CharT, TraitsT, AllocT> st{x, allocator};
std::basic_string_view<CharT, TraitsT> sv{y};

std::same_as<std::basic_string<CharT, TraitsT, AllocT>> decltype(auto) result = st + sv;
assert(result == expected);
assert(result.get_allocator() == allocator);
LIBCPP_ASSERT(is_string_asan_correct(st + sv));
}
// string&& + string_view
{
std::basic_string<CharT, TraitsT, AllocT> st{x, allocator};
std::basic_string_view<CharT, TraitsT> sv{y};

std::same_as<std::basic_string<CharT, TraitsT, AllocT>> decltype(auto) result = std::move(st) + sv;
assert(result == expected);
assert(result.get_allocator() == allocator);
LIBCPP_ASSERT(is_string_asan_correct(std::move(st) + sv));
}
// string_view + string&
{
std::basic_string_view<CharT, TraitsT> sv{x};
std::basic_string<CharT, TraitsT, AllocT> st{y, allocator};

std::same_as<std::basic_string<CharT, TraitsT, AllocT>> decltype(auto) result = sv + st;
assert(result == expected);
assert(result.get_allocator() == allocator);
LIBCPP_ASSERT(is_string_asan_correct(sv + st));
}
// string_view + const string&
{
std::basic_string_view<CharT, TraitsT> sv{x};
const std::basic_string<CharT, TraitsT, AllocT> st{y, allocator};

std::same_as<std::basic_string<CharT, TraitsT, AllocT>> decltype(auto) result = sv + st;
assert(result == expected);
assert(result.get_allocator() == allocator);
LIBCPP_ASSERT(is_string_asan_correct(sv + st));
}
// string_view + string&&
{
// Create a `basic_string` to workaround clang bug:
// https://github.com/llvm/llvm-project/issues/92382
// Comparison between pointers to a string literal and some other object results in constant evaluation failure.
std::basic_string<CharT, TraitsT, AllocT> st_{x, allocator};
std::basic_string_view<CharT, TraitsT> sv{st_};
std::basic_string<CharT, TraitsT, AllocT> st{y, allocator};

std::same_as<std::basic_string<CharT, TraitsT, AllocT>> decltype(auto) result = sv + std::move(st);
assert(result == expected);
assert(result.get_allocator() == allocator);
LIBCPP_ASSERT(is_string_asan_correct(sv + std::move(st)));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it also make sense to test string + convertible_to_string_view ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure it would! Thank you for suggesting!

}

template <typename CharT, typename TraitsT, typename AllocT = std::allocator<CharT>>
constexpr void test() {
// Concatenate with an empty `string`/`string_view`
test<CharT, TraitsT, AllocT>(CS(""), CS(""), CS(""));
test<CharT, TraitsT, AllocT>(CS(""), CS("short"), CS("short"));
test<CharT, TraitsT, AllocT>(CS(""), CS("not so short"), CS("not so short"));
test<CharT, TraitsT, AllocT>(CS(""), CS("this is a much longer string"), CS("this is a much longer string"));

test<CharT, TraitsT, AllocT>(CS(""), CS(""), CS(""));
test<CharT, TraitsT, AllocT>(CS("short"), CS(""), CS("short"));
test<CharT, TraitsT, AllocT>(CS("not so short"), CS(""), CS("not so short"));
test<CharT, TraitsT, AllocT>(CS("this is a much longer string"), CS(""), CS("this is a much longer string"));

// Non empty
test<CharT, TraitsT, AllocT>(CS("B"), CS("D"), CS("BD"));
test<CharT, TraitsT, AllocT>(CS("zmt94"), CS("+hkt82"), CS("zmt94+hkt82"));
test<CharT, TraitsT, AllocT>(CS("not so short"), CS("+is not bad"), CS("not so short+is not bad"));
test<CharT, TraitsT, AllocT>(
CS("this is a much longer string"),
CS("+which is so much better"),
CS("this is a much longer string+which is so much better"));
}

template <typename CharT>
constexpr bool test() {
test<CharT, std::char_traits<CharT>>();
test<CharT, std::char_traits<CharT>, min_allocator<CharT>>();
test<CharT, std::char_traits<CharT>, safe_allocator<CharT>>();
test<CharT, std::char_traits<CharT>, test_allocator<CharT>>();

test<CharT, constexpr_char_traits<CharT>>();
test<CharT, constexpr_char_traits<CharT>, min_allocator<CharT>>();
test<CharT, constexpr_char_traits<CharT>, safe_allocator<CharT>>();
test<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>();

return true;
}

int main(int, char**) {
test<char>();
static_assert(test<char>());
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
test<wchar_t>();
static_assert(test<wchar_t>());
#endif
#ifndef TEST_HAS_NO_CHAR8_T
test<char8_t>();
static_assert(test<char8_t>());
#endif
test<char16_t>();
static_assert(test<char16_t>());
test<char32_t>();
static_assert(test<char32_t>());

return 0;
}
Loading
Loading