charconv.hh 6.58 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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
// -*- 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
    {
      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"
        manipulator(s);

        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
            return from_chars_result{first + s.tellg(), std::errc::result_out_of_range};
          else
            // extraction fails
            return from_chars_result{first, std::errc::invalid_argument};
        }

        return from_chars_result{first + s.tellg(), std::errc{}};
      }

      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"
        manipulator(s);

        // negative integer values in base != 10 are not correctly converted
        bool sign = value < T(0);
        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};

        auto str = (sign ? "-" : "") + s.str();
        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


    /// \brief Converts a character sequence to an integer value
    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)
    {
      return Impl::fromCharsImpl(first, last, value, [base](auto& s) { s >> std::setbase(base); });
    }

    /// \brief Converts a character sequence to a floating-point value
    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)
    {
      return Impl::fromCharsImpl(first, last, value, [fmt](auto& s)
      {
        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;
          DUNE_THROW(NotImplemented,
            "Reading hexfloat values not supported by many standard libraries currently.");
        }
      });
    }


    /// \brief Converts an integer value to a character sequence
    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)
    {
      return Impl::toCharsImpl(first, last, value, [base](auto& s) { s << std::setbase(base); });
    }

    /// \brief Converts a floating-point value to a character sequence
    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)
    {
      return Impl::toCharsImpl(first, last, value, [](auto& /*s*/) {});
    }

    /// \brief Converts a floating-point value to a character sequence with additional format specifier
    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)
    {
      return Impl::toCharsImpl(first, last, value, [fmt](auto& s)
      {
        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;
      });
    }

    /// \brief Converts a floating-point value to a character sequence with format specifier and precision
    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)
    {
      return Impl::toCharsImpl(first, last, value, [fmt,precision](auto& s)
      {
        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;
      });
    }

#endif // DUNE_HAVE_CHARCONV

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

#endif // DUNE_COMMON_STD_CHARCONV_HH