Change `rstd::Char` into a `class`.
Before this CL, `rstd::Char` was a type alias for `std::uint32_t`.
After this CL `rstd::Char` is a separate `class` (that privately wraps
`std::uint32_t`). This change is a step toward a follow-up CL that will
start rejecting invalid bit patterns in constructors of `rstd::Char`.
PiperOrigin-RevId: 502991306
diff --git a/support/rstd/char.h b/support/rstd/char.h
index 2270ba0..d4c1450 100644
--- a/support/rstd/char.h
+++ b/support/rstd/char.h
@@ -10,13 +10,50 @@
namespace rstd {
// `rstd::Char` is a C++ representation of the `char` type from Rust.
-//
-// See "layout tests" comments in `char_test.cc` for explanation why `char32_t`
-// is not used.
-//
-// TODO(b/265338802): Reject `char` values with invalid bit patterns (possibly
-// retaining `constexpr` aspect of some conversions).
-using Char = std::uint32_t;
+class Char final {
+ public:
+ // TODO(b/265338802): Reject `char` values that may represent a part of a
+ // UTF-8 character (i.e. only the first 0-127 ASCII characters should be
+ // accepted).
+ constexpr explicit Char(char c) : value_(c) {}
+
+ // TODO(b/265338802): Reject `char` values with invalid bit patterns
+ // (retaining the `constexpr` aspect if possible).
+ constexpr explicit Char(char16_t c) : value_(c) {}
+ constexpr explicit Char(char32_t c) : value_(c) {}
+
+ constexpr Char(const Char&) = default;
+ constexpr Char& operator=(const Char&) = default;
+ constexpr Char(Char&&) = default;
+ constexpr Char& operator=(Char&&) = default;
+ ~Char() = default;
+
+ explicit constexpr operator std::uint32_t() const { return value_; }
+
+ constexpr bool operator==(const Char& other) const {
+ return value_ == other.value_;
+ }
+ constexpr bool operator!=(const Char& other) const {
+ return value_ != other.value_;
+ }
+ constexpr bool operator<=(const Char& other) const {
+ return value_ <= other.value_;
+ }
+ constexpr bool operator<(const Char& other) const {
+ return value_ < other.value_;
+ }
+ constexpr bool operator>=(const Char& other) const {
+ return value_ >= other.value_;
+ }
+ constexpr bool operator>(const Char& other) const {
+ return value_ > other.value_;
+ }
+
+ private:
+ // See "layout tests" comments in `char_test.cc` for explanation why
+ // `char32_t` is not used.
+ std::uint32_t value_;
+};
} // namespace rstd
diff --git a/support/rstd/char_test.cc b/support/rstd/char_test.cc
index bc84094..4db685f 100644
--- a/support/rstd/char_test.cc
+++ b/support/rstd/char_test.cc
@@ -32,12 +32,10 @@
// value".
//
// We don't map Rust's `char` to C++ `char32_t` because
-// - It may be wider than 32 bits - <internal link>/c/string/multibyte/char32_t says
-// that "char32_t is an unsigned integer type used for 32-bit wide characters
-// and is the same type as uint_least32_t. uint_least32_t is the smallest
-// unsigned integer type with width of at least 32 bits"
-// - It is problematic on MacOS - https://github.com/eqrion/cbindgen/issues/423
-// points out that `uchar.h` is missing on that platform.
+// https://en.cppreference.com/w/cpp/language/types#char32_t points out that the
+// builtin `char32_t` type "has the same size, signedness, and alignment as
+// std::uint_least32_t" (and therefore it is not guaranteed to be exactly
+// 32-bits wide as required for ABI-compatibility with Rust).
static_assert(sizeof(rstd::Char) == 4);
static_assert(alignof(rstd::Char) == 4);
static_assert(std::is_standard_layout_v<rstd::Char>);
@@ -50,7 +48,7 @@
// - the representation of c-char in the execution character set (until C++23)
// - the corresponding code point from ordinary literal encoding (since C++23).
TEST(RsCharTest, FromAsciiLiteral) {
- rstd::Char c = 'x';
+ const rstd::Char c('x');
EXPECT_EQ(0x78, static_cast<uint32_t>(c));
}
@@ -63,7 +61,7 @@
// with a single UTF-8 code unit (that is, c-char is in the range 0x0-0x7F,
// inclusive).
TEST(RsCharTest, FromUtf8Literal) {
- rstd::Char c = u8'x';
+ const rstd::Char c(u8'x');
EXPECT_EQ(0x78, static_cast<uint32_t>(c));
}
@@ -79,7 +77,7 @@
// Not testing `is_trivially_constructible`, because UTF-16 literals may
// fail Rust's well-formed-ness checks (e.g. they may represent only one
// part of a surrogate pair).
- rstd::Char c = u'Ł';
+ const rstd::Char c(u'Ł');
EXPECT_EQ(0x141, static_cast<uint32_t>(c));
}
@@ -92,14 +90,14 @@
// Not testing `is_trivially_constructible`, because UTF-32 literals may fail
// Rust's well-formed-ness checks (e.g. they may exceed the value of Rust's
// `std::char::MAX`).
- rstd::Char c = U'🦀';
+ const rstd::Char c(U'🦀');
EXPECT_EQ(0x1F980, static_cast<uint32_t>(c));
}
// Test that `rstd::Char` values can be compared with other `rstd::Char` values.
TEST(RsCharTest, ComparisonWithAnotherRsChar) {
- const rstd::Char a = 'a';
- const rstd::Char b = 'b';
+ const rstd::Char a('a');
+ const rstd::Char b('b');
EXPECT_TRUE(a == a);
EXPECT_FALSE(a != a);