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

Implement P2291R3 constexpr Integral <charconv> #3049

Merged
merged 9 commits into from
Aug 27, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
83 changes: 43 additions & 40 deletions stl/inc/charconv
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ inline constexpr char _Charconv_digits[] = {'0', '1', '2', '3', '4', '5', '6', '
_STL_INTERNAL_STATIC_ASSERT(_STD size(_Charconv_digits) == 36);

template <class _RawTy>
_NODISCARD to_chars_result _Integer_to_chars(
_NODISCARD _CONSTEXPR23 to_chars_result _Integer_to_chars(
char* _First, char* const _Last, const _RawTy _Raw_value, const int _Base) noexcept {
_Adl_verify_range(_First, _Last);
_STL_ASSERT(_Base >= 2 && _Base <= 36, "invalid base in to_chars()");
Expand Down Expand Up @@ -152,52 +152,55 @@ _NODISCARD to_chars_result _Integer_to_chars(
return {_Last, errc::value_too_large};
}

_CSTD memcpy(_First, _RNext, static_cast<size_t>(_Digits_written));
_Copy_n_unchecked4(_RNext, static_cast<size_t>(_Digits_written), _First);
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

return {_First + _Digits_written, errc{}};
}

inline to_chars_result to_chars(char* const _First, char* const _Last, const char _Value, const int _Base = 10) noexcept
_CONSTEXPR23 to_chars_result to_chars(
char* const _First, char* const _Last, const char _Value, const int _Base = 10) noexcept
/* strengthened */ {
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
inline to_chars_result to_chars(
_CONSTEXPR23 to_chars_result to_chars(
char* const _First, char* const _Last, const signed char _Value, const int _Base = 10) noexcept /* strengthened */ {
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
inline to_chars_result to_chars(char* const _First, char* const _Last, const unsigned char _Value,
_CONSTEXPR23 to_chars_result to_chars(char* const _First, char* const _Last, const unsigned char _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
inline to_chars_result to_chars(
_CONSTEXPR23 to_chars_result to_chars(
char* const _First, char* const _Last, const short _Value, const int _Base = 10) noexcept /* strengthened */ {
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
inline to_chars_result to_chars(char* const _First, char* const _Last, const unsigned short _Value,
_CONSTEXPR23 to_chars_result to_chars(char* const _First, char* const _Last, const unsigned short _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
inline to_chars_result to_chars(char* const _First, char* const _Last, const int _Value, const int _Base = 10) noexcept
_CONSTEXPR23 to_chars_result to_chars(
char* const _First, char* const _Last, const int _Value, const int _Base = 10) noexcept
/* strengthened */ {
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
inline to_chars_result to_chars(char* const _First, char* const _Last, const unsigned int _Value,
_CONSTEXPR23 to_chars_result to_chars(char* const _First, char* const _Last, const unsigned int _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
inline to_chars_result to_chars(char* const _First, char* const _Last, const long _Value, const int _Base = 10) noexcept
_CONSTEXPR23 to_chars_result to_chars(
char* const _First, char* const _Last, const long _Value, const int _Base = 10) noexcept
/* strengthened */ {
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
inline to_chars_result to_chars(char* const _First, char* const _Last, const unsigned long _Value,
_CONSTEXPR23 to_chars_result to_chars(char* const _First, char* const _Last, const unsigned long _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
inline to_chars_result to_chars(
_CONSTEXPR23 to_chars_result to_chars(
char* const _First, char* const _Last, const long long _Value, const int _Base = 10) noexcept /* strengthened */ {
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
inline to_chars_result to_chars(char* const _First, char* const _Last, const unsigned long long _Value,
_CONSTEXPR23 to_chars_result to_chars(char* const _First, char* const _Last, const unsigned long long _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_to_chars(_First, _Last, _Value, _Base);
}
Expand All @@ -213,27 +216,27 @@ struct from_chars_result {
#endif // _HAS_CXX20
};

_NODISCARD inline unsigned char _Digit_from_char(const char _Ch) noexcept {
// convert ['0', '9'] ['A', 'Z'] ['a', 'z'] to [0, 35], everything else to 255
inline constexpr unsigned char _Digit_from_byte[] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255,
255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255};
_STL_INTERNAL_STATIC_ASSERT(_STD size(_Digit_from_byte) == 256);

_NODISCARD _CONSTEXPR23 unsigned char _Digit_from_char(const char _Ch) noexcept {
// convert ['0', '9'] ['A', 'Z'] ['a', 'z'] to [0, 35], everything else to 255
static constexpr unsigned char _Digit_from_byte[] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255,
255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255};
_STL_INTERNAL_STATIC_ASSERT(_STD size(_Digit_from_byte) == 256);

return _Digit_from_byte[static_cast<unsigned char>(_Ch)];
}

template <class _RawTy>
_NODISCARD from_chars_result _Integer_from_chars(
_NODISCARD _CONSTEXPR23 from_chars_result _Integer_from_chars(
const char* const _First, const char* const _Last, _RawTy& _Raw_value, const int _Base) noexcept {
_Adl_verify_range(_First, _Last);
_STL_ASSERT(_Base >= 2 && _Base <= 36, "invalid base in from_chars()");
Expand Down Expand Up @@ -309,47 +312,47 @@ _NODISCARD from_chars_result _Integer_from_chars(
return {_Next, errc{}};
}

inline from_chars_result from_chars(
_CONSTEXPR23 from_chars_result from_chars(
const char* const _First, const char* const _Last, char& _Value, const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
inline from_chars_result from_chars(const char* const _First, const char* const _Last, signed char& _Value,
_CONSTEXPR23 from_chars_result from_chars(const char* const _First, const char* const _Last, signed char& _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
inline from_chars_result from_chars(const char* const _First, const char* const _Last, unsigned char& _Value,
_CONSTEXPR23 from_chars_result from_chars(const char* const _First, const char* const _Last, unsigned char& _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
inline from_chars_result from_chars(const char* const _First, const char* const _Last, short& _Value,
_CONSTEXPR23 from_chars_result from_chars(const char* const _First, const char* const _Last, short& _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
inline from_chars_result from_chars(const char* const _First, const char* const _Last, unsigned short& _Value,
_CONSTEXPR23 from_chars_result from_chars(const char* const _First, const char* const _Last, unsigned short& _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
inline from_chars_result from_chars(
_CONSTEXPR23 from_chars_result from_chars(
const char* const _First, const char* const _Last, int& _Value, const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
inline from_chars_result from_chars(const char* const _First, const char* const _Last, unsigned int& _Value,
_CONSTEXPR23 from_chars_result from_chars(const char* const _First, const char* const _Last, unsigned int& _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
inline from_chars_result from_chars(
_CONSTEXPR23 from_chars_result from_chars(
const char* const _First, const char* const _Last, long& _Value, const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
inline from_chars_result from_chars(const char* const _First, const char* const _Last, unsigned long& _Value,
_CONSTEXPR23 from_chars_result from_chars(const char* const _First, const char* const _Last, unsigned long& _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
inline from_chars_result from_chars(const char* const _First, const char* const _Last, long long& _Value,
_CONSTEXPR23 from_chars_result from_chars(const char* const _First, const char* const _Last, long long& _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
inline from_chars_result from_chars(const char* const _First, const char* const _Last, unsigned long long& _Value,
_CONSTEXPR23 from_chars_result from_chars(const char* const _First, const char* const _Last, unsigned long long& _Value,
const int _Base = 10) noexcept /* strengthened */ {
return _Integer_from_chars(_First, _Last, _Value, _Base);
}
Expand Down
2 changes: 2 additions & 0 deletions stl/inc/yvals_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@
// P2166R1 Prohibiting basic_string And basic_string_view Construction From nullptr
// P2186R2 Removing Garbage Collection Support
// P2273R3 constexpr unique_ptr
// P2291R3 constexpr Integral <charconv>
// P2302R4 ranges::contains, ranges::contains_subrange
// P2321R2 zip
// (changes to pair, tuple, and vector<bool>::reference only)
Expand Down Expand Up @@ -1496,6 +1497,7 @@ _EMIT_STL_ERROR(STL1004, "C++98 unexpected() is incompatible with C++23 unexpect
#define __cpp_lib_bind_back 202202L
#define __cpp_lib_byteswap 202110L
#define __cpp_lib_constexpr_bitset 202207L
#define __cpp_lib_constexpr_charconv 202207L
#define __cpp_lib_constexpr_typeinfo 202106L

#ifdef __cpp_lib_concepts
Expand Down
84 changes: 57 additions & 27 deletions tests/std/tests/P0067R5_charconv/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,11 @@ void test_common_to_chars(
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we can avoid using internal mechanisms in test file by replacing them with the following... (no change requested)

Suggested change
#if _HAS_CXX23 // testing constexpr overloads for integral types in C++23 (P2291R3)
#define CONSTEXPR23 constexpr
constexpr is_runtime_evaluated() noexcept {
return !is_constant_evaluated();
}
#else // ^^^ _HAS_CXX23 / vvv !_HAS_CXX23
#define CONSTEXPR23 inline
constexpr is_runtime_evaluated() noexcept {
return true;
}
#endif // _HAS_CXX23

Copy link
Member

Choose a reason for hiding this comment

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

Yes, we could do something like that in the future, possibly centralized in a lightweight classic header.

For the time being, tests don't need to strenuously avoid internal machinery and macros as long as there is reasonable justification.

template <typename T>
void test_integer_to_chars(const T value, const optional<int> opt_base, const string_view correct) {

test_common_to_chars(value, opt_base, nullopt, correct);
_CONSTEXPR23 void test_integer_to_chars(const T value, const optional<int> opt_base, const string_view correct) {
// This reaches constexpr step limit to quickly
CaseyCarter marked this conversation as resolved.
Show resolved Hide resolved
if (!_Is_constant_evaluated()) {
test_common_to_chars(value, opt_base, nullopt, correct);
}

{ // Also test successful from_chars() scenarios.
const char* const correct_first = correct.data();
Expand Down Expand Up @@ -387,7 +389,7 @@ constexpr pair<int64_t, array<const char*, 37>> output_negative[] = {
};

template <typename T>
void test_integer_to_chars() {
_CONSTEXPR23 bool test_integer_to_chars() {
for (int base = 2; base <= 36; ++base) {
test_integer_to_chars(static_cast<T>(0), base, "0");
test_integer_to_chars(static_cast<T>(1), base, "1");
Expand All @@ -413,12 +415,14 @@ void test_integer_to_chars() {
}

test_integer_to_chars(static_cast<T>(42), nullopt, "42");

return true;
}

enum class TestFromCharsMode { Normal, SignalingNaN };

template <typename T, typename BaseOrFmt>
void test_from_chars(const string_view input, const BaseOrFmt base_or_fmt, const size_t correct_idx,
_CONSTEXPR23 void test_from_chars(const string_view input, const BaseOrFmt base_or_fmt, const size_t correct_idx,
const errc correct_ec, const optional<T> opt_correct = nullopt,
const TestFromCharsMode mode = TestFromCharsMode::Normal) {

Expand Down Expand Up @@ -460,7 +464,17 @@ constexpr errc inv_arg = errc::invalid_argument;
constexpr errc out_ran = errc::result_out_of_range;

template <typename T>
void test_integer_from_chars() {
_CONSTEXPR23 bool test_integer_from_chars() {
string hundred_zeroes = string(100, '0');
string hundred_zeroes_and_11 = hundred_zeroes + "11"s;
string minus_hundred_zeroes = "-"s + hundred_zeroes;
string minus_hundred_zeroes_and_11 = "-"s + hundred_zeroes_and_11;

string hundred_ones = string(100, '1');
string hundred_ones_and_atatat = hundred_ones + "@@@"s;
string minus_hundred_ones = "-"s + hundred_ones;
string minus_hundred_ones_and_atatat = "-"s + hundred_ones_and_atatat;
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved

for (int base = 2; base <= 36; ++base) {
test_from_chars<T>("", base, 0, inv_arg); // no characters
test_from_chars<T>("@1", base, 0, inv_arg); // '@' is bogus
Expand Down Expand Up @@ -497,14 +511,14 @@ void test_integer_from_chars() {
}

// Test leading zeroes.
test_from_chars<T>(string(100, '0'), base, 100, errc{}, static_cast<T>(0));
test_from_chars<T>(string(100, '0') + "11"s, base, 102, errc{}, static_cast<T>(base + 1));
test_from_chars<T>(hundred_zeroes, base, 100, errc{}, static_cast<T>(0));
test_from_chars<T>(hundred_zeroes_and_11, base, 102, errc{}, static_cast<T>(base + 1));

// Test negative zero and negative leading zeroes.
if constexpr (is_signed_v<T>) {
test_from_chars<T>("-0", base, 2, errc{}, static_cast<T>(0));
test_from_chars<T>("-"s + string(100, '0'), base, 101, errc{}, static_cast<T>(0));
test_from_chars<T>("-"s + string(100, '0') + "11"s, base, 103, errc{}, static_cast<T>(-base - 1));
test_from_chars<T>(minus_hundred_zeroes, base, 101, errc{}, static_cast<T>(0));
test_from_chars<T>(minus_hundred_zeroes_and_11, base, 103, errc{}, static_cast<T>(-base - 1));
}

// N4713 23.20.3 [charconv.from.chars]/1 "The member ptr of the return value points to the
Expand All @@ -513,12 +527,12 @@ void test_integer_from_chars() {
test_from_chars<T>("11@@@", base, 2, errc{}, static_cast<T>(base + 1));

// When overflowing, we need to keep consuming valid digits, in order to return ptr correctly.
test_from_chars<T>(string(100, '1'), base, 100, out_ran);
test_from_chars<T>(string(100, '1') + "@@@"s, base, 100, out_ran);
test_from_chars<T>(hundred_ones, base, 100, out_ran);
test_from_chars<T>(hundred_ones_and_atatat, base, 100, out_ran);

if constexpr (is_signed_v<T>) {
test_from_chars<T>("-"s + string(100, '1'), base, 101, out_ran);
test_from_chars<T>("-"s + string(100, '1') + "@@@"s, base, 101, out_ran);
test_from_chars<T>(minus_hundred_ones, base, 101, out_ran);
test_from_chars<T>(minus_hundred_ones_and_atatat, base, 101, out_ran);
}
}

Expand All @@ -538,27 +552,22 @@ void test_integer_from_chars() {
test_from_chars<T>("-0x1729", 16, 2, errc{}, static_cast<T>(0)); // reads "-0", stops at 'x'
test_from_chars<T>("-0X1729", 16, 2, errc{}, static_cast<T>(0)); // reads "-0", stops at 'X'
}

return true;
}

template <typename T>
void test_integer() {
test_integer_to_chars<T>();
test_integer_from_chars<T>();
}

void all_integer_tests() {
test_integer<char>();
test_integer<signed char>();
test_integer<unsigned char>();
test_integer<short>();
test_integer<unsigned short>();
test_integer<int>();
test_integer<unsigned int>();
test_integer<long>();
test_integer<unsigned long>();
test_integer<long long>();
test_integer<unsigned long long>();
#if _HAS_CXX23
static_assert(test_integer_to_chars<T>());
static_assert(test_integer_from_chars<T>());
#endif // _HAS_CXX23
}

_CONSTEXPR23 bool test_integer_overflow_scenarios() {
// Test overflow scenarios.
test_from_chars<unsigned int>("4294967289", 10, 10, errc{}, 4294967289U); // not risky
test_from_chars<unsigned int>("4294967294", 10, 10, errc{}, 4294967294U); // risky with good digit
Expand All @@ -577,6 +586,27 @@ void all_integer_tests() {
test_from_chars<int>("-2147483648", 10, 11, errc{}, -2147483647 - 1); // risky with max digit
test_from_chars<int>("-2147483649", 10, 11, out_ran); // risky with bad digit
test_from_chars<int>("-2147483650", 10, 11, out_ran); // beyond risky

return true;
}

void all_integer_tests() {
test_integer<char>();
test_integer<signed char>();
test_integer<unsigned char>();
test_integer<short>();
test_integer<unsigned short>();
test_integer<int>();
test_integer<unsigned int>();
test_integer<long>();
test_integer<unsigned long>();
test_integer<long long>();
test_integer<unsigned long long>();

test_integer_overflow_scenarios();
#if _HAS_CXX23
static_assert(test_integer_overflow_scenarios());
#endif // _HAS_CXX23
}

void assert_message_bits(const bool b, const char* const msg, const uint32_t bits) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,20 @@ STATIC_ASSERT(__cpp_lib_constexpr_bitset == 202207L);
#endif
#endif

#if _HAS_CXX23
#ifndef __cpp_lib_constexpr_charconv
#error __cpp_lib_constexpr_charconv is not defined
#elif __cpp_lib_constexpr_charconv != 202207L
#error __cpp_lib_constexpr_charconv is not 202207L
#else
STATIC_ASSERT(__cpp_lib_constexpr_charconv == 202207L);
#endif
#else
#ifdef __cpp_lib_constexpr_charconv
#error __cpp_lib_constexpr_charconv is defined
#endif
#endif

#if _HAS_CXX20
#ifndef __cpp_lib_constexpr_complex
#error __cpp_lib_constexpr_complex is not defined
Expand Down