charconv.hh 9.04 KB
Newer Older
Praetorius, Simon's avatar
Praetorius, Simon committed
1 2 3 4 5 6 7 8 9 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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=4 sw=2 sts=2:
#ifndef DUNE_COMMON_STD_CHARCONV_HH
#define DUNE_COMMON_STD_CHARCONV_HH

/** \file
  * \brief Implements a backport of c++17's to_chars and from_chars facilities for a locale-independent
  * string to/from number conversion, respectively.
  **/

#include <utility>

#ifdef __has_include
  #if __has_include(<charconv>) && defined(__cpp_lib_to_chars) && !defined(DUNE_NO_STD_CHARCONV)
    #define DUNE_HAVE_CHARCONV 1
  #endif
#endif

#ifdef DUNE_HAVE_CHARCONV
  #include <charconv>
#else
  #include <algorithm>
  #include <iomanip>
  #include <ios>
  #include <iterator>
  #include <limits>
  #include <locale>
  #include <sstream>
  #include <system_error>
  #include <type_traits>
  #include <utility>
#endif

namespace Dune
{
  namespace Std
  {

#ifdef DUNE_HAVE_CHARCONV

    using chars_format = std::chars_format;

    using from_chars_result = std::from_chars_result;
    using std::from_chars;

    using to_chars_result = std::to_chars_result;
    using std::to_chars;

#else // DUNE_HAVE_CHARCONV

    // A BitmaskType used to specify floating-point formatting for to_chars and from_chars
    enum class chars_format : int
    {
      scientific = 1,
      fixed = 2,
      hex = 4,
      general = fixed | scientific
    };

    struct from_chars_result
    {
      const char* ptr;
      std::errc ec;
    };

    struct to_chars_result
    {
      char* ptr;
      std::errc ec;
    };

    namespace Impl
    {
74 75 76 77 78 79
      /**
       * \brief Performs the conversion of a character sequence [first, last) to
       * a value of type `T` by using a `std::istringstream` formatted stream extracter.
       * with format flags set by the `manipulator`.
       *
       * On success, returns a value of type \ref from_chars_result such that `ptr`
80
       * has the value equal to `last` if all characters match and `ec` is
81 82 83 84 85 86 87 88
       * value-initialized.
       *
       * If there is no pattern match, returns a value of type \ref from_chars_result
       * such that `ptr` equals `first` and `ec` equals `std::errc::invalid_argument`.
       * `value` is unmodified.
       *
       * If the pattern was matched, but the parsed value is not in the range representable
       * by the type of `value`, returns value of type \ref from_chars_result such
89 90
       * that `ec` equals ´std::errc::result_out_of_range` and `ptr` has value equal
       * to `last`. `value` is unmodified.
91 92
       *
       * NOTE: Can not determine pointer to the first character not matching the
93
       *       pattern, as required by `std::from_chars`. Returning `last` if all
94 95
       *       characters are consumed.
       **/
Praetorius, Simon's avatar
Praetorius, Simon committed
96 97 98 99 100 101
      template<typename T, typename StreamManipulator>
      from_chars_result fromCharsImpl (const char* first, const char* last, T& value,
                                       StreamManipulator manipulator)
      {
        std::istringstream s(std::string(first,last));
        s.imbue(std::locale::classic()); // make sure we are in locale "C"
102 103
        if (!manipulator(s))
          return from_chars_result{first, std::errc::not_supported};
Praetorius, Simon's avatar
Praetorius, Simon committed
104 105 106 107 108 109 110 111 112 113

        try {
          s >> value; // extract value
        } catch (...) {
          s.setstate(std::ios_base::failbit);
        }

        if (s.fail()) {
          if (value == std::numeric_limits<T>::max() || value == std::numeric_limits<T>::min())
            // range error
114
            return from_chars_result{s.eof() ? last : first, std::errc::result_out_of_range};
Praetorius, Simon's avatar
Praetorius, Simon committed
115 116 117 118 119
          else
            // extraction fails
            return from_chars_result{first, std::errc::invalid_argument};
        }

120
        return from_chars_result{s.eof() ? last : first, std::errc{}};
Praetorius, Simon's avatar
Praetorius, Simon committed
121 122
      }

123 124 125 126 127 128 129 130 131 132 133 134 135 136
      /**
       * \brief Performas a conversion of `value` of type `T` to a character
       * sequence stored in the memory range [first, last) by using a
       * `std::ostringstream` formatted stream inserter with format flags set by
       * the `manipulator`.
       *
       * On success, returns a value of type \ref to_chars_result such that `ec`
       * equals value-initialized `std::errc` and `ptr` is the one-past-the-end
       * pointer of the characters written. Note that the string is not NUL-terminated.
       *
       * On error, returns a value of type \ref to_chars_result holding
       * `std::errc::value_too_large` in `ec`, a copy of the value `last` in `ptr`,
       * and leaves the contents of the range [first, last) in unspecified state.
       **/
Praetorius, Simon's avatar
Praetorius, Simon committed
137 138 139 140 141 142
      template<typename T, typename StreamManipulator>
      to_chars_result toCharsImpl (char* first, char* last, T value,
                                   StreamManipulator manipulator)
      {
        std::ostringstream s;
        s.imbue(std::locale::classic()); // make sure we are in locale "C"
143 144
        if (!manipulator(s))
          return to_chars_result{last, std::errc::not_supported};
Praetorius, Simon's avatar
Praetorius, Simon committed
145 146

        // negative integer values in base != 10 are not correctly converted
147
        const bool sign = value < T(0);
Praetorius, Simon's avatar
Praetorius, Simon committed
148 149 150 151 152 153 154 155 156
        try {
          s << (sign ? -value : value);
        } catch (...) {
          s.setstate(std::ios_base::failbit);
        }

        if (s.fail())
          return to_chars_result{last, std::errc::value_too_large};

157
        auto str = (sign ? '-' + s.str() : s.str());
Praetorius, Simon's avatar
Praetorius, Simon committed
158 159 160 161 162 163 164 165 166 167
        auto num = std::min(std::distance(first, last),
                            std::distance(str.begin(), str.end()));
        std::copy_n(str.begin(), num, first);

        return to_chars_result{first + num, std::errc{}};
      }

    } // end namespace Impl


168 169
    /// \brief Converts a character sequence to an integer value.
    /// \see Impl::fromCharsImpl
Praetorius, Simon's avatar
Praetorius, Simon committed
170 171 172 173
    template<typename T,
      std::enable_if_t<std::is_integral<T>::value, int> = 0>
    from_chars_result from_chars (const char* first, const char* last, T& value, int base = 10)
    {
174
      return Impl::fromCharsImpl(first, last, value, [base](auto& s)
175 176
      {
        s >> std::setbase(base);
177
        return true;
178
      });
Praetorius, Simon's avatar
Praetorius, Simon committed
179 180 181
    }

    /// \brief Converts a character sequence to a floating-point value
182 183 184
    /// \see Impl::fromCharsImpl
    // NOTE: Reading hexfloat values not supported by many standard libraries, currently.
    //       So, `chars_format::hex` results in an error.
Praetorius, Simon's avatar
Praetorius, Simon committed
185 186 187 188 189
    template<typename T,
      std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
    from_chars_result from_chars (const char* first, const char* last, T& value,
                                  chars_format fmt = chars_format::general)
    {
190
      return Impl::fromCharsImpl(first, last, value, [fmt](auto& s)
Praetorius, Simon's avatar
Praetorius, Simon committed
191 192 193 194 195 196 197
      {
        if ((int(fmt) & int(chars_format::scientific)) != 0)
          s >> std::scientific;
        if ((int(fmt) & int(chars_format::fixed)) != 0)
          s >> std::fixed;
        if ((int(fmt) & int(chars_format::hex)) != 0) {
          s >> std::hexfloat;
198
          return false;
Praetorius, Simon's avatar
Praetorius, Simon committed
199
        }
200
        return true;
Praetorius, Simon's avatar
Praetorius, Simon committed
201 202 203 204 205
      });
    }


    /// \brief Converts an integer value to a character sequence
206
    /// \see Impl::toCharsImpl
Praetorius, Simon's avatar
Praetorius, Simon committed
207 208 209 210
    template <class T,
      std::enable_if_t<std::is_integral<T>::value, int> = 0>
    to_chars_result to_chars (char* first, char* last, T value, int base = 10)
    {
211
      return Impl::toCharsImpl(first, last, value, [base](auto& s)
212 213
      {
        s << std::setbase(base);
214
        return true;
215
      });
Praetorius, Simon's avatar
Praetorius, Simon committed
216 217 218
    }

    /// \brief Converts a floating-point value to a character sequence
219
    /// \see Impl::toCharsImpl
Praetorius, Simon's avatar
Praetorius, Simon committed
220 221 222 223
    template <class T,
      std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
    to_chars_result to_chars (char* first, char* last, T value)
    {
224
      return Impl::toCharsImpl(first, last, value, [](auto& /*s*/) { return true; });
Praetorius, Simon's avatar
Praetorius, Simon committed
225 226 227
    }

    /// \brief Converts a floating-point value to a character sequence with additional format specifier
228
    /// \see Impl::toCharsImpl
Praetorius, Simon's avatar
Praetorius, Simon committed
229 230 231 232
    template <class T,
      std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
    to_chars_result to_chars (char* first, char* last, T value, chars_format fmt)
    {
233
      return Impl::toCharsImpl(first, last, value, [fmt](auto& s)
Praetorius, Simon's avatar
Praetorius, Simon committed
234 235 236 237 238 239 240
      {
        if ((int(fmt) & int(chars_format::scientific)) != 0)
          s << std::scientific;
        if ((int(fmt) & int(chars_format::fixed)) != 0)
          s << std::fixed;
        if ((int(fmt) & int(chars_format::hex)) != 0)
          s << std::hexfloat;
241
        return true;
Praetorius, Simon's avatar
Praetorius, Simon committed
242 243 244 245
      });
    }

    /// \brief Converts a floating-point value to a character sequence with format specifier and precision
246
    /// \see Impl::toCharsImpl
Praetorius, Simon's avatar
Praetorius, Simon committed
247 248 249 250
    template <class T,
      std::enable_if_t<std::is_floating_point<T>::value, int> = 0>
    to_chars_result to_chars (char* first, char* last, T value, chars_format fmt, int precision)
    {
251
      return Impl::toCharsImpl(first, last, value, [fmt,precision](auto& s)
Praetorius, Simon's avatar
Praetorius, Simon committed
252 253 254 255 256 257 258 259
      {
        s << std::setprecision(precision);
        if ((int(fmt) & int(chars_format::scientific)) != 0)
          s << std::scientific;
        if ((int(fmt) & int(chars_format::fixed)) != 0)
          s << std::fixed;
        if ((int(fmt) & int(chars_format::hex)) != 0)
          s << std::hexfloat;
260
        return true;
Praetorius, Simon's avatar
Praetorius, Simon committed
261 262 263 264 265 266 267 268 269
      });
    }

#endif // DUNE_HAVE_CHARCONV

  } // end namespace Std
} // end namespace Dune

#endif // DUNE_COMMON_STD_CHARCONV_HH