diff --git a/CMakeLists.txt b/CMakeLists.txt index d299f57..36335f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ target_include_directories(boost_charconv PUBLIC include) target_link_libraries(boost_charconv PUBLIC Boost::config + Boost::assert ) target_compile_features(boost_charconv PUBLIC cxx_std_11) diff --git a/doc/charconv.adoc b/doc/charconv.adoc index b3bd8c4..022fd17 100644 --- a/doc/charconv.adoc +++ b/doc/charconv.adoc @@ -4,8 +4,8 @@ Distributed under the Boost Software License, Version 1.0. https://www.boost.org/LICENSE_1_0.txt //// -# CharConv: An Implementation of in {cpp}11 -Peter Dimov += CharConv: An Implementation of in {cpp}11 +Peter Dimov and Matt Borland :toc: left :toclevels: 4 :idprefix: @@ -17,6 +17,7 @@ Peter Dimov :leveloffset: +1 include::charconv/overview.adoc[] +include::charconv/from_chars.adoc[] include::charconv/reference.adoc[] include::charconv/copyright.adoc[] diff --git a/doc/charconv.html b/doc/charconv.html new file mode 100644 index 0000000..5fc9ee3 --- /dev/null +++ b/doc/charconv.html @@ -0,0 +1,981 @@ + + + + + + + + +CharConv: An Implementation of <charconv> in C++11 + + + + + + +
+
+

Overview

+
+
+

Description

+
+

Charconv is a collection of parsing functions that are locale-independent, non-allocating, and non-throwing.

+
+
+
+

Usage Examples

+
+
+
#include <boost/charconv.hpp>
+
+const char* buffer = "42";
+int v = 0;
+auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v);
+assert(r == 42);
+
+
+
+
+

Supported Compilers

+
+
    +
  • +

    GCC 5 or later with -std=c++11 or above

    +
  • +
  • +

    Clang 3.9 or later with -std=c++11 or above

    +
  • +
  • +

    Visual Studio 2015, 2017, 2019

    +
  • +
+
+
+

Tested on Github Actions.

+
+
+
+
+
+

from_chars

+
+
+

from_chars overview

+
+
+
struct from_chars_result
+{
+    const char* ptr;
+    int ec;
+
+    friend constexpr bool operator==(const from_chars_result& lhs, const from_chars_result& rhs) noexcept
+    friend constexpr bool operator!=(const from_chars_result& lhs, const from_chars_result& rhs) noexcept
+}
+
+template <typename Integral>
+BOOST_CXX14_CONSTEXPR from_chars_result from_chars(const char* first, const char* last, Integral& value, int base = 10) noexcept;
+
+BOOST_CXX14_CONSTEXPR from_chars_result from_chars<bool>(const char* first, const char* last, bool& value, int base) = delete;
+
+
+
+
+

from_chars_result

+
+
    +
  • +

    ptr - points to the first character not matching the pattern, or has the value of last if all characters are successfully parsed.

    +
  • +
  • +

    ec - the error code. Valid values for <cerrno> are:

    +
    +
      +
    • +

      0 - successful parsing

      +
    • +
    • +

      EINVAL - invalid argument (e.g. parsing a negative number into an unsigned type)

      +
    • +
    • +

      ERANGE - result out of range (e.g. overflow)

      +
    • +
    +
    +
  • +
  • +

    operator== - compares the values of ptr and ec for equality

    +
  • +
  • +

    operator!- - compares the value of ptr and ec for inequality

    +
  • +
+
+
+
+

from_chars

+
+
    +
  • +

    first, last - valid range to parse

    +
  • +
  • +

    value - where the output is stored upon successful parsing

    +
  • +
  • +

    base - the integer base to use. Must be between 2 and 36 inclusive

    +
  • +
  • +

    from_chars for integral types is constexpr when compiled using -std=c++14 or newer

    +
    +
      +
    • +

      One known exception is GCC 5 which does not support constexpr comparison of const char*.

      +
    • +
    +
    +
  • +
+
+
+
+

from_chars for integral types

+
+
    +
  • +

    All built-in integral types are allowed except bool which is deleted

    +
  • +
  • +

    These function have been tested to support int128 and unsigned int128 when compiling with -std=gnu++11 or newer

    +
  • +
+
+
+
+

Examples

+
+

Basic usage

+
+
+
const char* buffer = "42";
+int v = 0;
+from_chars_result r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v);
+assert(r.ec == 0);
+assert(v == 42);
+
+
+
+
+

Hexadecimal

+
+
+
const char* buffer = "2a";
+unsigned v = 0;
+auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v, 16);
+assert(r.ec == 0);
+assert(v == 42);
+
+
+
+
+

EINVAL

+
+
+
const char* buffer = "-123";
+unsigned v = 0;
+auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v);
+assert(r.ec, EINVAL);
+assert(v == 0);
+
+
+
+

In the event of EINVAL v is not set by from_chars

+
+
+
+

ERANGE

+
+
+
const char* buffer = "1234";
+unsigned char v = 0;
+auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v);
+assert(r.ec == ERANGE);
+assert(v == 0)
+
+
+
+

In the event of ERANGE v is not set by from_chars

+
+
+
+
+
+
+

Reference

+
+
+

<boost/charconv/from_chars.hpp>

+
+

Synopsis

+
+
+
namespace boost {
+namespace charconv {
+
+struct from_chars_result;
+
+template <typename Integral>
+BOOST_CXX14_CONSTEXPR from_chars_result from_chars(const char* first, const char* last, Integral& value, int base = 10) noexcept;
+
+// ...
+
+} // namespace charconv
+} // namespace boost
+
+
+
+
+
+

<boost/charconv/to_chars.hpp>

+
+

Synopsis

+
+
+
namespace boost {
+namespace charconv {
+
+struct to_chars_result;
+
+template<class Integral>
+to_chars_result to_chars( char* first, char* last, Integral value, int base = 10 );
+
+// ...
+
+} // namespace charconv
+} // namespace boost
+
+
+
+
+
+

<boost/charconv.hpp>

+
+

This convenience header includes all headers previously +mentioned.

+
+
+
+
+
+ +
+
+

This documentation is copyright 2022-2023 Peter Dimov and Matt Borland and is distributed under +the Boost Software License, Version 1.0.

+
+
+
+
+ + + + \ No newline at end of file diff --git a/doc/charconv/copyright.adoc b/doc/charconv/copyright.adoc index 2defdc8..5e4a1c4 100644 --- a/doc/charconv/copyright.adoc +++ b/doc/charconv/copyright.adoc @@ -8,5 +8,5 @@ https://www.boost.org/LICENSE_1_0.txt # Copyright and License :idprefix: -This documentation is copyright 2022 Peter Dimov and is distributed under +This documentation is copyright 2022-2023 Peter Dimov and Matt Borland and is distributed under the http://www.boost.org/LICENSE_1_0.txt[Boost Software License, Version 1.0]. diff --git a/doc/charconv/from_chars.adoc b/doc/charconv/from_chars.adoc new file mode 100644 index 0000000..704702b --- /dev/null +++ b/doc/charconv/from_chars.adoc @@ -0,0 +1,86 @@ +//// +Copyright 2023 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + += from_chars +:idprefix: from_chars_ + +== from_chars overview +``` +struct from_chars_result +{ + const char* ptr; + int ec; + + friend constexpr bool operator==(const from_chars_result& lhs, const from_chars_result& rhs) noexcept + friend constexpr bool operator!=(const from_chars_result& lhs, const from_chars_result& rhs) noexcept +} + +template +BOOST_CXX14_CONSTEXPR from_chars_result from_chars(const char* first, const char* last, Integral& value, int base = 10) noexcept; + +BOOST_CXX14_CONSTEXPR from_chars_result from_chars(const char* first, const char* last, bool& value, int base) = delete; +``` + +== from_chars_result +* ptr - points to the first character not matching the pattern, or has the value of last if all characters are successfully parsed. +* ec - the error code. Valid values for are: +** 0 - successful parsing +** EINVAL - invalid argument (e.g. parsing a negative number into an unsigned type) +** ERANGE - result out of range (e.g. overflow) +* operator== - compares the values of ptr and ec for equality +* operator!- - compares the value of ptr and ec for inequality + +== from_chars +* first, last - valid range to parse +* value - where the output is stored upon successful parsing +* base - the integer base to use. Must be between 2 and 36 inclusive +* from_chars for integral types is constexpr when compiled using `-std=c++14` or newer +** One known exception is GCC 5 which does not support constexpr comparison of const char*. + +== from_chars for integral types +* All built-in integral types are allowed except bool which is deleted +* These function have been tested to support `__int128` and `unsigned __int128` when compiling with `-std=gnu++11` or newer + +== Examples + +=== Basic usage +``` +const char* buffer = "42"; +int v = 0; +from_chars_result r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v); +assert(r.ec == 0); +assert(v == 42); +``` + +=== Hexadecimal +``` +const char* buffer = "2a"; +unsigned v = 0; +auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v, 16); +assert(r.ec == 0); +assert(v == 42); +``` + +=== EINVAL +``` +const char* buffer = "-123"; +unsigned v = 0; +auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v); +assert(r.ec, EINVAL); +assert(v == 0); +``` +In the event of EINVAL v is not set by `from_chars` + + +=== ERANGE +``` +const char* buffer = "1234"; +unsigned char v = 0; +auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v); +assert(r.ec == ERANGE); +assert(v == 0) +``` +In the event of ERANGE v is not set by `from_chars` diff --git a/doc/charconv/overview.adoc b/doc/charconv/overview.adoc index 9b3da93..7c14230 100644 --- a/doc/charconv/overview.adoc +++ b/doc/charconv/overview.adoc @@ -5,22 +5,28 @@ https://www.boost.org/LICENSE_1_0.txt //// [#overview] -# Overview += Overview :idprefix: overview_ -## Description +== Description -... +Charconv is a collection of parsing functions that are locale-independent, non-allocating, and non-throwing. -## Usage Examples +== Usage Examples -... +``` +#include -## Supported Compilers +const char* buffer = "42"; +int v = 0; +auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v); +assert(r == 42); +``` + +== Supported Compilers * GCC 5 or later with `-std=c++11` or above * Clang 3.9 or later with `-std=c++11` or above * Visual Studio 2015, 2017, 2019 -Tested on https://github.com/cppalliance/charconv/actions[Github Actions] and -https://ci.appveyor.com/project/cppalliance/charconv[Appveyor]. +Tested on https://github.com/cppalliance/charconv/actions[Github Actions]. diff --git a/doc/charconv/reference.adoc b/doc/charconv/reference.adoc index b9c8266..1e7c89b 100644 --- a/doc/charconv/reference.adoc +++ b/doc/charconv/reference.adoc @@ -5,12 +5,12 @@ https://www.boost.org/LICENSE_1_0.txt //// [#reference] -# Reference += Reference :idprefix: ref_ -## +== -### Synopsis +=== Synopsis ``` namespace boost { @@ -18,8 +18,8 @@ namespace charconv { struct from_chars_result; -template -from_chars_result from_chars( const char* first, const char* last, Integral& value, int base = 10 ); +template +BOOST_CXX14_CONSTEXPR from_chars_result from_chars(const char* first, const char* last, Integral& value, int base = 10) noexcept; // ... @@ -27,9 +27,9 @@ from_chars_result from_chars( const char* first, const char* last, Integral& val } // namespace boost ``` -## +== -### Synopsis +=== Synopsis ``` namespace boost { @@ -46,7 +46,7 @@ to_chars_result to_chars( char* first, char* last, Integral value, int base = 10 } // namespace boost ``` -## +== This convenience header includes all headers previously mentioned. diff --git a/include/boost/charconv/config.hpp b/include/boost/charconv/config.hpp index ffcccf2..bbafb51 100644 --- a/include/boost/charconv/config.hpp +++ b/include/boost/charconv/config.hpp @@ -34,4 +34,14 @@ #endif +#ifndef BOOST_CHARCONV_STANDALONE +# include +# define BOOST_CHARCONV_ASSERT(expr) BOOST_ASSERT(expr) +# define BOOST_CHARCONV_ASSERT_MSG(expr, msg) BOOST_ASSERT_MSG(expr, msg) +#else // Use plain asserts +# include +# define BOOST_CHARCONV_ASSERT(expr) assert(expr) +# define BOOST_CHARCONV_ASSERT_MSG(expr, msg) assert((expr)&&(msg)) +#endif + #endif // BOOST_CHARCONV_CONFIG_HPP_INCLUDED diff --git a/include/boost/charconv/from_chars.hpp b/include/boost/charconv/from_chars.hpp index ecd3d08..9e5fcc6 100644 --- a/include/boost/charconv/from_chars.hpp +++ b/include/boost/charconv/from_chars.hpp @@ -7,6 +7,14 @@ #define BOOST_CHARCONV_FROM_CHARS_HPP_INCLUDED #include +#include +#include +#include +#include +#include +#include +#include +#include namespace boost { namespace charconv { @@ -15,20 +23,200 @@ namespace boost { namespace charconv { struct from_chars_result { const char* ptr; + + // Values: + // 0 = no error + // EINVAL = invalid_argument + // ERANGE = result_out_of_range int ec; - - friend bool operator==(const from_chars_result& lhs, const from_chars_result& rhs) + + friend constexpr bool operator==(const from_chars_result& lhs, const from_chars_result& rhs) noexcept { return lhs.ptr == rhs.ptr && lhs.ec == rhs.ec; } - friend bool operator!=(const from_chars_result& lhs, const from_chars_result& rhs) + friend constexpr bool operator!=(const from_chars_result& lhs, const from_chars_result& rhs) noexcept { return !(lhs == rhs); } }; -BOOST_CHARCONV_DECL from_chars_result from_chars(const char* first, const char* last, int& value, int base = 10); +namespace detail { + +static constexpr std::array uchar_values = + {{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 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}}; + +// Convert characters for 0-9, A-Z, a-z to 0-35. Anything else is 255 +constexpr unsigned char digit_from_char(char val) noexcept +{ + return uchar_values[static_cast(val)]; +} + +template ::value, bool>::type = true> +constexpr Integer apply_sign(Integer val) noexcept +{ + return -val; +} + +template ::value, bool>::type = true> +constexpr Integer apply_sign(Integer val) noexcept +{ + return val; +} + +template +BOOST_CXX14_CONSTEXPR boost::charconv::from_chars_result from_chars_integer_impl(const char* first, const char* last, Integer& value, int base) noexcept +{ + using Unsigned_Integer = typename std::make_unsigned::type; + Unsigned_Integer result = 0; + Unsigned_Integer overflow_value = 0; + Unsigned_Integer max_digit = 0; + + // Check pre-conditions + BOOST_CHARCONV_ASSERT_MSG(base >= 2 && base <= 36, "Base must be between 2 and 36 (inclusive)"); + if (!(first <= last)) + { + return {first, EINVAL}; + } + + // Strip sign if the type is signed + // Negative sign will be appended at the end of parsing + BOOST_ATTRIBUTE_UNUSED bool is_negative = false; + auto next = first; + BOOST_IF_CONSTEXPR (std::is_signed::value) + { + if (next != last) + { + if (*next == '-') + { + is_negative = true; + ++next; + } + else if (*next == '+') + { + ++next; + } + } + + overflow_value = (std::numeric_limits::max)(); + max_digit = (std::numeric_limits::max)(); + + if (is_negative) + { + ++overflow_value; + ++max_digit; + } + } + else + { + if (next != last) + { + if (*next == '-') + { + return {first, EINVAL}; + } + else if (*next == '+') + { + ++next; + } + } + + overflow_value = (std::numeric_limits::max)(); + max_digit = (std::numeric_limits::max)(); + } + + overflow_value /= static_cast(base); + max_digit %= base; + + // If the only character was a sign abort now + if (next == last) + { + return {first, EINVAL}; + } + + bool overflowed = false; + while (next != last) + { + auto current_digit = digit_from_char(*next); + + if (current_digit >= base) + { + break; + } + + if (result < overflow_value || (result == overflow_value && current_digit <= max_digit)) + { + result = static_cast(result * base + current_digit); + } + else + { + // Required to keep updating the value of next, but the result is garbage + overflowed = true; + } + + ++next; + } + + // Return the parsed value, adding the sign back if applicable + // If we have overflowed then we do not return the result + if (overflowed) + { + return {next, ERANGE}; + } + + value = static_cast(result); + BOOST_IF_CONSTEXPR (std::is_signed::value) + { + if (is_negative) + { + value = apply_sign(value); + } + } + + return {next, 0}; +} + +} // Namespace detail + +// GCC 5 does not support constexpr comparison of const char* +#if defined(__GNUC__) && __GNUC__ == 5 +template ::value, bool>::type = true> +inline from_chars_result from_chars(const char* first, const char* last, Integer& value, int base = 10) noexcept +{ + return detail::from_chars_integer_impl(first, last, value, base); +} + +template<> +inline from_chars_result from_chars(const char* first, const char* last, bool& value, int base) noexcept = delete; + +#else + +// Only from_chars for integer types is constexpr (as of C++23) +template ::value, bool>::type = true> +BOOST_CXX14_CONSTEXPR from_chars_result from_chars(const char* first, const char* last, Integer& value, int base = 10) noexcept +{ + return detail::from_chars_integer_impl(first, last, value, base); +} + +template <> +BOOST_CXX14_CONSTEXPR from_chars_result from_chars(const char* first, const char* last, bool& value, int base) noexcept = delete; + +#endif // GCC5 workarounds } // namespace charconv } // namespace boost diff --git a/reporting/performance/charcov_benchmark b/reporting/performance/charcov_benchmark new file mode 100755 index 0000000..326eb54 Binary files /dev/null and b/reporting/performance/charcov_benchmark differ diff --git a/src/from_chars.cpp b/src/from_chars.cpp index 97f93de..9c94412 100644 --- a/src/from_chars.cpp +++ b/src/from_chars.cpp @@ -4,10 +4,10 @@ // https://www.boost.org/LICENSE_1_0.txt #include +#include +#include +#include +#include #include - -boost::charconv::from_chars_result boost::charconv::from_chars(const char* first, const char* last, int& value, int base) -{ - value = static_cast(std::strtol(first, const_cast(&last), base)); - return { last, 0 }; -} +#include +#include diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b8cee3d..6e2c396 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,6 @@ include(BoostTestJamfile OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST) if(HAVE_BOOST_TEST) -boost_test_jamfile(FILE Jamfile LINK_LIBRARIES Boost::charconv Boost::core) +boost_test_jamfile(FILE Jamfile LINK_LIBRARIES Boost::charconv Boost::core Boost::assert) endif() diff --git a/test/Jamfile b/test/Jamfile index 680ffaf..5bdc53b 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -20,3 +20,4 @@ run quick.cpp ; run from_chars.cpp ; run to_chars.cpp ; run roundtrip.cpp ; +run from_chars_STL_comp.cpp : : : [ requires cxx17_std_apply ] ; diff --git a/test/from_chars.cpp b/test/from_chars.cpp index 7b79989..23ac5ad 100644 --- a/test/from_chars.cpp +++ b/test/from_chars.cpp @@ -5,25 +5,188 @@ #include #include +#include +#include #include +#include +#include +#include -int main() +#ifdef __GLIBCXX_TYPE_INT_N_0 +template +void test_128bit_int() { - const char* buffer = "1048576"; + const char* buffer1 = "85070591730234615865843651857942052864"; // 2^126 + T test_value = 1; + test_value = test_value << 126; + T v1 = 0; + auto r1 = boost::charconv::from_chars(buffer1, buffer1 + std::strlen(buffer1), v1); + BOOST_TEST(r1.ec == 0); + BOOST_TEST(v1 == test_value); + BOOST_TEST(std::numeric_limits::max() > static_cast(std::numeric_limits::max())); +} +#endif // 128-bit testing - int v = 0; +#ifndef BOOST_NO_CXX14_CONSTEXPR +template +constexpr std::pair constexpr_test_helper() +{ + const char* buffer1 = "42"; + T v1 = 0; + auto r1 = boost::charconv::from_chars(buffer1, buffer1 + 2, v1); + + return std::make_pair(v1, r1); +} + +template +constexpr void constexpr_test() +{ + constexpr auto results = constexpr_test_helper(); + static_assert(results.second.ec == 0, "No error"); + static_assert(results.first == 42, "Value is 42"); +} + +#endif + +template +void base2_test() +{ + // Includes leading 0 which should be ignored + const char* buffer1 = "0101010"; + T v1 = 0; + auto r1 = boost::charconv::from_chars(buffer1, buffer1 + std::strlen(buffer1), v1, 2); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_EQ(v1, 42); +} + +template +void base16_test() +{ + // In base 16 0x and 0X prefixes are not allowed + const char* buffer1 = "2a"; + T v1 = 0; + auto r1 = boost::charconv::from_chars(buffer1, buffer1 + std::strlen(buffer1), v1, 16); + BOOST_TEST_EQ(r1.ec, 0); + BOOST_TEST_EQ(v1, 42); + + const char* buffer2 = "0"; + T v2 = 1; + auto r2 = boost::charconv::from_chars(buffer2, buffer2 + std::strlen(buffer2), v2, 16); + BOOST_TEST_EQ(r2.ec, 0); + BOOST_TEST_EQ(v2, 0); +} + +template +void overflow_test() +{ + const char* buffer1 = "1234"; + T v1 = 0; + auto r1 = boost::charconv::from_chars(buffer1, buffer1 + std::strlen(buffer1), v1); + + BOOST_IF_CONSTEXPR((std::numeric_limits::max)() < 1234) + { + BOOST_TEST_EQ(r1.ec, ERANGE); + } + else + { + BOOST_TEST_EQ(r1.ec, 0) && BOOST_TEST_EQ(v1, 1234); + } + + const char* buffer2 = "123456789123456789123456789"; + T v2 = 0; + auto r2 = boost::charconv::from_chars(buffer2, buffer2 + std::strlen(buffer2), v2); + // In the event of overflow v2 is to be returned unmodified + BOOST_TEST_EQ(r2.ec, ERANGE) && BOOST_TEST_EQ(v2, 0); +} + +template +void invalid_argument_test() +{ + const char* buffer1 = ""; + T v1 = 0; + auto r1 = boost::charconv::from_chars(buffer1, buffer1 + std::strlen(buffer1), v1); + BOOST_TEST_EQ(r1.ec, EINVAL); + + const char* buffer2 = "-"; + T v2 = 0; + auto r2 = boost::charconv::from_chars(buffer2, buffer2 + std::strlen(buffer2), v2); + BOOST_TEST_EQ(r2.ec, EINVAL); + + const char* buffer3 = "+"; + T v3 = 0; + auto r3 = boost::charconv::from_chars(buffer3, buffer3 + std::strlen(buffer3), v3); + BOOST_TEST_EQ(r3.ec, EINVAL); + + BOOST_IF_CONSTEXPR(std::is_unsigned::value) + { + const char* buffer4 = "-123"; + T v4 = 0; + auto r4 = boost::charconv::from_chars(buffer4, buffer4 + std::strlen(buffer4), v4); + BOOST_TEST_EQ(r4.ec, EINVAL); + } +} + +// No overflows, negative numbers, locales, etc. +template +void simple_test() +{ + const char* buffer = "34"; + + T v = 0; auto r = boost::charconv::from_chars(buffer, buffer + std::strlen(buffer), v); - BOOST_TEST_EQ( r.ec, 0 ) && BOOST_TEST_EQ( v, 1048576 ); + BOOST_TEST_EQ( r.ec, 0 ) && BOOST_TEST_EQ(v, 34); BOOST_TEST(r == r); boost::charconv::from_chars_result r2 {r.ptr, 0}; BOOST_TEST(r == r2); - const char* buffer2 = "1234567"; - int v2 = 0; + const char* buffer2 = "12"; + T v2 = 0; auto r3 = boost::charconv::from_chars(buffer2, buffer2 + std::strlen(buffer), v2); BOOST_TEST(r != r3); + BOOST_TEST_EQ(r3.ec, 0) && BOOST_TEST_EQ(v2, 12); +} + +int main() +{ + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + simple_test(); + + invalid_argument_test(); + invalid_argument_test(); + + overflow_test(); + overflow_test(); + + base16_test(); + base16_test(); + + base2_test(); + base2_test(); + + #if !(defined(__GNUC__) && __GNUC__ == 5) + # ifndef BOOST_NO_CXX14_CONSTEXPR + constexpr_test(); + # endif + #endif + + // Only compiles using cxxstd-dialect=gnu or equivalent + #ifdef __GLIBCXX_TYPE_INT_N_0 + test_128bit_int<__int128>(); + test_128bit_int(); + #endif return boost::report_errors(); } diff --git a/test/from_chars_STL_comp.cpp b/test/from_chars_STL_comp.cpp new file mode 100644 index 0000000..7823a34 --- /dev/null +++ b/test/from_chars_STL_comp.cpp @@ -0,0 +1,98 @@ +// Copyright 2023 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include + +#if !defined(BOOST_NO_CXX17_HDR_CHARCONV) && (!defined(__clang_major__) || (defined(__clang_major__) && __clang_major__ > 7)) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void test() +{ + // Binary + const char* base2 = "0101010"; + T base2_v_boost = 0; + T base2_v_stl = 0; + auto r1_boost = boost::charconv::from_chars(base2, base2 + std::strlen(base2), base2_v_boost, 2); + auto r1_stl = std::from_chars(base2, base2 + std::strlen(base2), base2_v_stl, 2); + BOOST_TEST_EQ(r1_boost.ptr, r1_stl.ptr); + BOOST_TEST_EQ(base2_v_boost, base2_v_stl); + + // Hexadecimal + const char* base16 = "0x2a"; + T base16_v_boost = 0; + T base16_v_stl = 0; + auto r2_boost = boost::charconv::from_chars(base16, base16 + std::strlen(base16), base16_v_boost, 16); + auto r2_stl = std::from_chars(base16, base16 + std::strlen(base16), base16_v_stl, 16); + BOOST_TEST_EQ(r2_boost.ptr, r2_stl.ptr); + BOOST_TEST_EQ(base16_v_boost, base16_v_stl); + + base16 = "2a"; + r2_boost = boost::charconv::from_chars(base16, base16 + std::strlen(base16), base16_v_boost, 16); + r2_stl = std::from_chars(base16, base16 + std::strlen(base16), base16_v_stl, 16); + BOOST_TEST_EQ(r2_boost.ptr, r2_stl.ptr); + BOOST_TEST_EQ(base16_v_boost, base16_v_stl); + + // Decimal + const char* base10 = "42"; + T base10_v_boost = 0; + T base10_v_stl = 0; + auto r3_boost = boost::charconv::from_chars(base10, base10 + std::strlen(base10), base10_v_boost); + auto r3_stl = std::from_chars(base10, base10 + std::strlen(base10), base10_v_stl); + BOOST_TEST_EQ(r3_boost.ptr, r3_stl.ptr); + BOOST_TEST_EQ(base10_v_boost, base10_v_stl); + + // Negative number + const char* neg = "-100"; + T negative_v_boost = 0; + T negative_v_stl = 0; + auto r4_boost = boost::charconv::from_chars(neg, neg + std::strlen(neg), negative_v_boost); + auto r4_stl = std::from_chars(neg, neg + std::strlen(neg), negative_v_stl); + BOOST_TEST_EQ(r4_boost.ptr, r4_stl.ptr); + BOOST_TEST_EQ(negative_v_boost, negative_v_stl); + + // Digit seperator + const char* sep = "1'000"; + T sep_v_boost = 0; + T sep_v_stl = 0; + auto r5_boost = boost::charconv::from_chars(sep, sep + std::strlen(sep), sep_v_boost); + auto r5_stl = std::from_chars(sep, sep + std::strlen(sep), sep_v_stl); + BOOST_TEST_EQ(r5_boost.ptr, r5_stl.ptr); + BOOST_TEST_EQ(sep_v_boost, sep_v_stl); +} + +int main() +{ + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + test(); + + return boost::report_errors(); +} + +#else + +int main() +{ + return 0; +} + +#endif