Skip to content

Commit cce062d

Browse files
authored
[libc++] Reintroduce the removed std::char_traits specialization (llvm#66153)
This partially reverts commit e30a148, which removed the base template for std::char_traits. That base template had been marked as deprecated since LLVM 16 and we were planning to remove it in LLVM 18. However, as explained in the post-commit comments in https://reviews.llvm.org/D157058, the deprecation mechanism didn't work as expected. Basically, the deprecation warnings were never shown to users since libc++ headers are system headers and Clang doesn't show warnings in system headers. As a result, this removal came with basically no lead time as far as users are concerned, which is a poor experience. For this reason, I am re-introducing the deprecated char_traits specialization until we have a proper way of phasing it out in a way that is not a surprise for users.
1 parent 2f005df commit cce062d

File tree

4 files changed

+280
-8
lines changed

4 files changed

+280
-8
lines changed

Diff for: libcxx/docs/ReleaseNotes/18.rst

+11-8
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,6 @@ Deprecations and Removals
8585
warning). ``_LIBCPP_ENABLE_ASSERTIONS`` will be removed entirely in the next release and setting it will become an
8686
error. See :ref:`the hardening documentation <using-hardening-modes>` for more details.
8787

88-
- The base template for ``std::char_traits`` has been removed. If you are using
89-
``std::char_traits`` with types other than ``char``, ``wchar_t``, ``char8_t``,
90-
``char16_t``, ``char32_t`` or a custom character type for which you
91-
specialized ``std::char_traits``, your code will no longer work. The Standard
92-
does not mandate that a base template is provided, and such a base template is
93-
bound to be incorrect for some types, which could previously cause unexpected
94-
behavior while going undetected.
95-
9688
Upcoming Deprecations and Removals
9789
----------------------------------
9890

@@ -109,6 +101,17 @@ LLVM 18
109101
and ``<experimental/vector>`` will be removed in LLVM 18, as all their contents will have been implemented in
110102
namespace ``std`` for at least two releases.
111103

104+
LLVM 19
105+
~~~~~~~
106+
107+
- The base template for ``std::char_traits`` has been marked as deprecated and will be removed in LLVM 19. If you
108+
are using ``std::char_traits`` with types other than ``char``, ``wchar_t``, ``char8_t``, ``char16_t``, ``char32_t``
109+
or a custom character type for which you specialized ``std::char_traits``, your code will stop working when we
110+
remove the base template. The Standard does not mandate that a base template is provided, and such a base template
111+
is bound to be incorrect for some types, which could currently cause unexpected behavior while going undetected.
112+
Note that the ``_LIBCPP_CHAR_TRAITS_REMOVE_BASE_SPECIALIZATION`` macro can be defined in LLVM 18 to eagerly remove
113+
the specialization and prepare code bases for the unconditional removal in LLVM 19.
114+
112115
ABI Affecting Changes
113116
---------------------
114117

Diff for: libcxx/include/__string/char_traits.h

+102
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,108 @@ exposition-only to document what members a char_traits specialization should pro
7171
};
7272
*/
7373

74+
//
75+
// Temporary extension to provide a base template for std::char_traits.
76+
// TODO(LLVM-19): Remove this class.
77+
//
78+
#if !defined(_LIBCPP_CHAR_TRAITS_REMOVE_BASE_SPECIALIZATION)
79+
template <class _CharT>
80+
struct _LIBCPP_DEPRECATED_("char_traits<T> for T not equal to char, wchar_t, char8_t, char16_t or char32_t is non-standard and is provided for a temporary period. It will be removed in LLVM 18, so please migrate off of it.")
81+
char_traits
82+
{
83+
using char_type = _CharT;
84+
using int_type = int;
85+
using off_type = streamoff;
86+
using pos_type = streampos;
87+
using state_type = mbstate_t;
88+
89+
static inline void _LIBCPP_CONSTEXPR_SINCE_CXX17 _LIBCPP_HIDE_FROM_ABI
90+
assign(char_type& __c1, const char_type& __c2) _NOEXCEPT {__c1 = __c2;}
91+
static inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR bool eq(char_type __c1, char_type __c2) _NOEXCEPT
92+
{return __c1 == __c2;}
93+
static inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR bool lt(char_type __c1, char_type __c2) _NOEXCEPT
94+
{return __c1 < __c2;}
95+
96+
static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17
97+
int compare(const char_type* __s1, const char_type* __s2, size_t __n) {
98+
for (; __n; --__n, ++__s1, ++__s2)
99+
{
100+
if (lt(*__s1, *__s2))
101+
return -1;
102+
if (lt(*__s2, *__s1))
103+
return 1;
104+
}
105+
return 0;
106+
}
107+
_LIBCPP_INLINE_VISIBILITY static _LIBCPP_CONSTEXPR_SINCE_CXX17
108+
size_t length(const char_type* __s) {
109+
size_t __len = 0;
110+
for (; !eq(*__s, char_type(0)); ++__s)
111+
++__len;
112+
return __len;
113+
}
114+
_LIBCPP_INLINE_VISIBILITY static _LIBCPP_CONSTEXPR_SINCE_CXX17
115+
const char_type* find(const char_type* __s, size_t __n, const char_type& __a) {
116+
for (; __n; --__n)
117+
{
118+
if (eq(*__s, __a))
119+
return __s;
120+
++__s;
121+
}
122+
return nullptr;
123+
}
124+
static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
125+
char_type* move(char_type* __s1, const char_type* __s2, size_t __n) {
126+
if (__n == 0) return __s1;
127+
char_type* __r = __s1;
128+
if (__s1 < __s2)
129+
{
130+
for (; __n; --__n, ++__s1, ++__s2)
131+
assign(*__s1, *__s2);
132+
}
133+
else if (__s2 < __s1)
134+
{
135+
__s1 += __n;
136+
__s2 += __n;
137+
for (; __n; --__n)
138+
assign(*--__s1, *--__s2);
139+
}
140+
return __r;
141+
}
142+
_LIBCPP_INLINE_VISIBILITY
143+
static _LIBCPP_CONSTEXPR_SINCE_CXX20
144+
char_type* copy(char_type* __s1, const char_type* __s2, size_t __n) {
145+
if (!__libcpp_is_constant_evaluated()) {
146+
_LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(
147+
__s2 < __s1 || __s2 >= __s1 + __n, "char_traits::copy overlapped range");
148+
}
149+
char_type* __r = __s1;
150+
for (; __n; --__n, ++__s1, ++__s2)
151+
assign(*__s1, *__s2);
152+
return __r;
153+
}
154+
_LIBCPP_INLINE_VISIBILITY
155+
static _LIBCPP_CONSTEXPR_SINCE_CXX20
156+
char_type* assign(char_type* __s, size_t __n, char_type __a) {
157+
char_type* __r = __s;
158+
for (; __n; --__n, ++__s)
159+
assign(*__s, __a);
160+
return __r;
161+
}
162+
163+
static inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR int_type not_eof(int_type __c) _NOEXCEPT
164+
{return eq_int_type(__c, eof()) ? ~eof() : __c;}
165+
static inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR char_type to_char_type(int_type __c) _NOEXCEPT
166+
{return char_type(__c);}
167+
static inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR int_type to_int_type(char_type __c) _NOEXCEPT
168+
{return int_type(__c);}
169+
static inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR bool eq_int_type(int_type __c1, int_type __c2) _NOEXCEPT
170+
{return __c1 == __c2;}
171+
static inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR int_type eof() _NOEXCEPT
172+
{return int_type(EOF);}
173+
};
174+
#endif // !defined(_LIBCPP_CHAR_TRAITS_REMOVE_BASE_SPECIALIZATION)
175+
74176
// char_traits<char>
75177

76178
template <>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
// <string>
10+
11+
// template<> struct char_traits<T> for arbitrary T
12+
13+
// Make sure we issue deprecation warnings.
14+
15+
#include <string>
16+
17+
void f() {
18+
std::char_traits<unsigned char> t1; (void)t1; // expected-warning{{'char_traits<unsigned char>' is deprecated}}
19+
std::char_traits<signed char> t2; (void)t2; // expected-warning{{'char_traits<signed char>' is deprecated}}
20+
std::char_traits<unsigned long> t3; (void)t3; // expected-warning{{'char_traits<unsigned long>' is deprecated}}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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+
// <string>
10+
11+
// template<> struct char_traits<T> for arbitrary T
12+
13+
// Non-standard but provided temporarily for users to migrate.
14+
15+
// ADDITIONAL_COMPILE_FLAGS: -Wno-deprecated
16+
17+
#include <string>
18+
#include <cassert>
19+
#include <type_traits>
20+
21+
#include "test_macros.h"
22+
23+
template <class Char>
24+
TEST_CONSTEXPR_CXX20 bool test() {
25+
static_assert(std::is_same<typename std::char_traits<Char>::char_type, Char>::value, "");
26+
static_assert(std::is_same<typename std::char_traits<Char>::int_type, int>::value, "");
27+
static_assert(std::is_same<typename std::char_traits<Char>::off_type, std::streamoff>::value, "");
28+
static_assert(std::is_same<typename std::char_traits<Char>::pos_type, std::streampos>::value, "");
29+
static_assert(std::is_same<typename std::char_traits<Char>::state_type, std::mbstate_t>::value, "");
30+
31+
assert(std::char_traits<Char>::to_int_type(Char('a')) == Char('a'));
32+
assert(std::char_traits<Char>::to_int_type(Char('A')) == Char('A'));
33+
assert(std::char_traits<Char>::to_int_type(0) == 0);
34+
35+
assert(std::char_traits<Char>::to_char_type(Char('a')) == Char('a'));
36+
assert(std::char_traits<Char>::to_char_type(Char('A')) == Char('A'));
37+
assert(std::char_traits<Char>::to_char_type(0) == 0);
38+
39+
assert(std::char_traits<Char>::eof() == EOF);
40+
41+
assert(std::char_traits<Char>::not_eof(Char('a')) == Char('a'));
42+
assert(std::char_traits<Char>::not_eof(Char('A')) == Char('A'));
43+
assert(std::char_traits<Char>::not_eof(0) == 0);
44+
assert(std::char_traits<Char>::not_eof(std::char_traits<Char>::eof()) !=
45+
std::char_traits<Char>::eof());
46+
47+
assert(std::char_traits<Char>::lt(Char('\0'), Char('A')) == (Char('\0') < Char('A')));
48+
assert(std::char_traits<Char>::lt(Char('A'), Char('\0')) == (Char('A') < Char('\0')));
49+
assert(std::char_traits<Char>::lt(Char('a'), Char('a')) == (Char('a') < Char('a')));
50+
assert(std::char_traits<Char>::lt(Char('A'), Char('a')) == (Char('A') < Char('a')));
51+
assert(std::char_traits<Char>::lt(Char('a'), Char('A')) == (Char('a') < Char('A')));
52+
53+
assert( std::char_traits<Char>::eq(Char('a'), Char('a')));
54+
assert(!std::char_traits<Char>::eq(Char('a'), Char('A')));
55+
56+
assert( std::char_traits<Char>::eq_int_type(Char('a'), Char('a')));
57+
assert(!std::char_traits<Char>::eq_int_type(Char('a'), Char('A')));
58+
assert(!std::char_traits<Char>::eq_int_type(std::char_traits<Char>::eof(), Char('A')));
59+
assert( std::char_traits<Char>::eq_int_type(std::char_traits<Char>::eof(), std::char_traits<Char>::eof()));
60+
61+
{
62+
Char s1[] = {1, 2, 3, 0};
63+
Char s2[] = {0};
64+
assert(std::char_traits<Char>::length(s1) == 3);
65+
assert(std::char_traits<Char>::length(s2) == 0);
66+
}
67+
68+
{
69+
Char s1[] = {1, 2, 3};
70+
assert(std::char_traits<Char>::find(s1, 3, Char(1)) == s1);
71+
assert(std::char_traits<Char>::find(s1, 3, Char(2)) == s1+1);
72+
assert(std::char_traits<Char>::find(s1, 3, Char(3)) == s1+2);
73+
assert(std::char_traits<Char>::find(s1, 3, Char(4)) == 0);
74+
assert(std::char_traits<Char>::find(s1, 3, Char(0)) == 0);
75+
assert(std::char_traits<Char>::find(NULL, 0, Char(0)) == 0);
76+
}
77+
78+
{
79+
Char s1[] = {1, 2, 3};
80+
Char s2[3] = {0};
81+
assert(std::char_traits<Char>::copy(s2, s1, 3) == s2);
82+
assert(s2[0] == Char(1));
83+
assert(s2[1] == Char(2));
84+
assert(s2[2] == Char(3));
85+
assert(std::char_traits<Char>::copy(NULL, s1, 0) == NULL);
86+
assert(std::char_traits<Char>::copy(s1, NULL, 0) == s1);
87+
}
88+
89+
{
90+
Char s1[] = {1, 2, 3};
91+
assert(std::char_traits<Char>::move(s1, s1+1, 2) == s1);
92+
assert(s1[0] == Char(2));
93+
assert(s1[1] == Char(3));
94+
assert(s1[2] == Char(3));
95+
s1[2] = Char(0);
96+
assert(std::char_traits<Char>::move(s1+1, s1, 2) == s1+1);
97+
assert(s1[0] == Char(2));
98+
assert(s1[1] == Char(2));
99+
assert(s1[2] == Char(3));
100+
assert(std::char_traits<Char>::move(NULL, s1, 0) == NULL);
101+
assert(std::char_traits<Char>::move(s1, NULL, 0) == s1);
102+
}
103+
104+
{
105+
Char s1[] = {0};
106+
assert(std::char_traits<Char>::compare(s1, s1, 0) == 0);
107+
assert(std::char_traits<Char>::compare(NULL, NULL, 0) == 0);
108+
109+
Char s2[] = {1, 0};
110+
Char s3[] = {2, 0};
111+
assert(std::char_traits<Char>::compare(s2, s2, 1) == 0);
112+
assert(std::char_traits<Char>::compare(s2, s3, 1) < 0);
113+
assert(std::char_traits<Char>::compare(s3, s2, 1) > 0);
114+
}
115+
116+
{
117+
Char s2[3] = {0};
118+
assert(std::char_traits<Char>::assign(s2, 3, Char(5)) == s2);
119+
assert(s2[0] == Char(5));
120+
assert(s2[1] == Char(5));
121+
assert(s2[2] == Char(5));
122+
assert(std::char_traits<Char>::assign(NULL, 0, Char(5)) == NULL);
123+
}
124+
125+
{
126+
Char c = Char('\0');
127+
std::char_traits<Char>::assign(c, Char('a'));
128+
assert(c == Char('a'));
129+
}
130+
131+
return true;
132+
}
133+
134+
int main(int, char**) {
135+
test<unsigned char>();
136+
test<signed char>();
137+
test<unsigned long>();
138+
139+
#if TEST_STD_VER > 17
140+
static_assert(test<unsigned char>());
141+
static_assert(test<signed char>());
142+
static_assert(test<unsigned long>());
143+
#endif
144+
145+
return 0;
146+
}

0 commit comments

Comments
 (0)