代码之家  ›  专栏  ›  技术社区  ›  Thomas

如何用c++14实现std::from_chars()

  •  0
  • Thomas  · 技术社区  · 6 年前

    我正在尝试将boost::string_视图转换为整数。 This post 讨论使用 from_chars() ,但是这是在C++ 17中可用的,我正在寻找C++ 14的解决方案。

    这里最好的选择是什么?

    0 回复  |  直到 6 年前
        1
  •  4
  •   sehe    6 年前

    本着 @t-niese's now-deleted answer 我建议采用精神疗法。因为C++ 14在表上,所以我们使用x3:

    template <typename Int = int, unsigned Radix=10>
    Int parse_int(std::string_view sv) {
        static constexpr boost::spirit::x3::int_parser<Int, Radix> p{};
        Int val;
        if (!parse(begin(sv), end(sv), p, val))
            throw std::runtime_error("parse_int");
        return val;
    }
    

    这件令人惊讶的小事却能做很多事。它可以解析为任何整数类型,包括非标准类型(如Boost Multiprecision、GMP或MPFR)。

    如果你真的想,你甚至可以让它把整数解析成非整数类型,尽管它不会解析非整数格式,请参阅 How to parse space-separated floats in C++ quickly? 如果你有兴趣的话。

    也可以在那里学习这些程序在实践中的表现。

    测试用例

    int main() {
        expect("0", 0);
        expect("+0", 0);
        expect("-0", 0);
        expect("+1", 1);
        expect("-1", -1);
        expect<int8_t>("-127", -127);
        expect<int8_t>("-128", -128);
    
        // edge case
        expect<uint8_t>("-1", -1);                     // surprising?
        expect<unsigned long>("-1", std::stoul("-1")); // Nope, matches stoul!
    
        auto std_roundtrip = [](auto value) { expect(std::to_string(value), value); };
        std_roundtrip(std::numeric_limits<intmax_t>::min());
        std_roundtrip(std::numeric_limits<intmax_t>::max());
        std_roundtrip(std::numeric_limits<uintmax_t>::min());
        std_roundtrip(std::numeric_limits<uintmax_t>::max());
    
        // radix
        expect<int, 2>("-01011", -11);
        expect<int, 8>("-01011", -521);
        expect<int, 16>("a0", 160);
    
        // invalids
        should_fail(""); // empty
        should_fail("+"); // lone sign
        should_fail("+ 9999"); // space
    
        // extensibility:
        using Large = boost::multiprecision::int1024_t;
        for (auto huge : { Large(42) << 700, -(Large(42) << 701) })
            expect(boost::lexical_cast<std::string>(huge), huge);
    
        // doesn't require the target type to be integral either
        using Decimal = boost::multiprecision::cpp_dec_float_50;
        expect<Decimal>("123456789", 123456789);
        // but it's still an integer parser:
        should_fail<Decimal>("1e10");
        should_fail<Decimal>("1.0");
    }
    

    印刷品

    "0" -> 0 OK
    "+0" -> 0 OK
    "-0" -> 0 OK
    "+1" -> 1 OK
    "-1" -> -1 OK
    "-127" ->  OK
    "-128" -> € OK
    "-1" -> ÿ OK
    "-1" -> 18446744073709551615 OK
    "-9223372036854775808" -> -9223372036854775808 OK
    "9223372036854775807" -> 9223372036854775807 OK
    "0" -> 0 OK
    "18446744073709551615" -> 18446744073709551615 OK
    "-01011" -> -11 OK
    "-01011" -> -521 OK
    "a0" -> 160 OK
     OK (should not parse)
     OK (should not parse)
     OK (should not parse)
    "220925707865031687304121575080965403953114271718573302098927797928886750480477394528773994523658714951533284691959485143946816154507719762251368220367378995698119394187394673124049877831141554125394316572902817792" -> 220925707865031687304121575080965403953114271718573302098927797928886750480477394528773994523658714951533284691959485143946816154507719762251368220367378995698119394187394673124049877831141554125394316572902817792 OK
    "-441851415730063374608243150161930807906228543437146604197855595857773500960954789057547989047317429903066569383918970287893632309015439524502736440734757991396238788374789346248099755662283108250788633145805635584" -> -441851415730063374608243150161930807906228543437146604197855595857773500960954789057547989047317429903066569383918970287893632309015439524502736440734757991396238788374789346248099755662283108250788633145805635584 OK
    "123456789" -> 1.23457e+08 OK
     OK (should not parse)
     OK (should not parse)
    

    Live On Coliru

    注意更新到更好的非抛出接口,该接口更新了string_视图,以反映输入的哪个部分被消耗(请参见下面的中的 奖励主题

    "3e10" -> 3 OK
     -> trailing "e10"
    "-7.0" -> -7 OK
     -> trailing ".0"
    
    #include <boost/spirit/home/x3.hpp>
    
    template <typename Int = int, unsigned Radix=10>
    static inline std::optional<Int> parse_int(std::string_view& remain) {
        static constexpr boost::spirit::x3::int_parser<Int, Radix> p{};
        Int val;
        auto f = begin(remain), l = end(remain);
        if (!parse(f, l, p, val))
            return std::nullopt;
        remain = remain.substr(f - begin(remain));
        return val;
    }
    
    #include <iostream>
    #include <iomanip>
    #include <boost/lexical_cast.hpp>
    
    template <typename Int>
    std::string to_string(Int const& value) {
        using widen = std::common_type_t<int, Int>; // pesky chars keep showing as non-numbers
        return boost::lexical_cast<std::string>(static_cast<widen>(value));
    }
    
    template <typename Int = int, unsigned Radix = 10>
    void expect(std::string_view input, Int expected) {
        std::cout << std::quoted(input);
        if (auto actual = parse_int<Int, Radix>(input)) {
            if (expected == actual.value())
                std::cout << " -> " << to_string(actual.value()) << " OK\n";
            else
                std::cout << " -> " << to_string(actual.value()) << " MISMATCH (" << to_string(expected) << " expected instead)\n";
    
            if (!input.empty())
                std::cout << " -> trailing " << std::quoted(input) << "\n";
        } else {
            std::cout << " FAILED (" << to_string(expected) << " expected instead)\n";
        }
    }
    
    template <typename Int = int, unsigned Radix = 10>
    void should_fail(std::string_view input) {
        std::cout << std::quoted(input);
        if (auto actual = parse_int<Int, Radix>(input)) {
            std::cout << " -> " << to_string(actual.value())
                << " MISMATCH (expected to fail parse instead)\n";
            if (!input.empty())
                std::cout << " -> trailing " << std::quoted(input) << "\n";
        } else {
            std::cout << " OK (should not parse)\n";
        }
    }
    
    #include <boost/multiprecision/cpp_dec_float.hpp>
    #include <boost/multiprecision/cpp_int.hpp>
    
    int main() {
        expect("0", 0);
        expect("+0", 0);
        expect("-0", 0);
        expect("+1", 1);
        expect("-1", -1);
        expect<int8_t>("-127", -127);
        expect<int8_t>("-128", -128);
    
        // edge case
        expect<uint8_t>("-1", -1);                     // surprising?
        expect<unsigned long>("-1", std::stoul("-1")); // Nope, matches stoul!
    
        auto std_roundtrip = [](auto value) { expect(std::to_string(value), value); };
        std_roundtrip(std::numeric_limits<intmax_t>::min());
        std_roundtrip(std::numeric_limits<intmax_t>::max());
        std_roundtrip(std::numeric_limits<uintmax_t>::min());
        std_roundtrip(std::numeric_limits<uintmax_t>::max());
    
        // radix
        expect<int, 2>("-01011", -11);
        expect<int, 8>("-01011", -521);
        expect<int, 16>("a0", 160);
    
        // invalids
        should_fail(""); // empty
        should_fail("+"); // lone sign
        should_fail("+ 9999"); // space
    
        // extensibility:
        using Large = boost::multiprecision::int1024_t;
        for (auto huge : { Large(42) << 700, -(Large(42) << 701) })
            expect(boost::lexical_cast<std::string>(huge), huge);
    
        // doesn't require the target type to be integral either
        using Decimal = boost::multiprecision::cpp_dec_float_50;
        expect<Decimal>("123456789", 123456789);
        // but it's still an integer parser:
        expect<Decimal>("3e10", 3);
        expect<Decimal>("-7.0", -7);
    }
    

    奖励主题

    • 严格解析无符号整数 -1 x3::int_parser 具有 x3::uint_parser

      template <typename Int = int, unsigned Radix=10>
      Int parse_uint(std::string_view sv) {
          static constexpr boost::spirit::x3::uint_parser<Int, Radix> p{};
          Int val;
          if (!parse(begin(sv), end(sv), p >> boost::spirit::x3::eoi, val))
              throw std::runtime_error("parse_int");
          return val;
      }
      
    • 为了得到 from_chars 如果你的下一个角色没有被分析,只需放下 x3::eoi

      template <typename Int = int, unsigned Radix=10>
      Int parse_int(std::string_view sv, std::string_view& remain) {
          static constexpr boost::spirit::x3::int_parser<Int, Radix> p{};
          Int val;
          auto f = begin(sv), l = end(sv);
          if (!parse(f, l, p, val))
              throw std::runtime_error("parse_int");
          remain = { &*f, size_t(std::distance(f,l)) };
          return val;
      }
      

      看看它的行为 Live On Coliru

      std::string_view input = "123bogus", remain;
      
      std::cout
          << "input: " << std::quoted(input) << " -> "
          << parse_int(input, remain)
          << " remaining: " << std::quoted(remain)
          << std::endl;
      

      印刷品

       input: "123bogus" -> 123 remaining: "bogus"
      
    • 实际上,事后想想, optional<> 可能比信号故障的例外情况要好得多:

        template <typename Int = int, unsigned Radix=10>
      static inline std::optional<Int> parse_int(std::string_view& remain) {
          static constexpr boost::spirit::x3::int_parser<Int, Radix> p{};
          Int val;
          auto f = begin(remain), l = end(remain);
          if (!parse(f, l, p, val))
              return std::nullopt;
          remain = remain.substr(f - begin(remain));
          return val;
      }
      

      int main() {
          auto input = "123bogus";
          std::string_view remain = input;
      
          if (auto parsed = parse_int(remain)) 
              printf("input: '%s' -> %d remaining: '%s'\n",
                      input, parsed.value(), remain.data());
      }
      

      编译 一直到 : Compiler Explorer

      .LC0:
              .string "123bogus"
      .LC1:
              .string "input: '%s' -> %d remaining: '%s'\n"
      main:
              sub     rsp, 8
              mov     ecx, OFFSET FLAT:.LC0+3
              mov     edx, 123
              xor     eax, eax
              mov     esi, OFFSET FLAT:.LC0
              mov     edi, OFFSET FLAT:.LC1
              call    printf
              xor     eax, eax
              add     rsp, 8
              ret
      
        2
  •  1
  •   t.niese    6 年前

    spirit::qi ,将迭代器给定的序列解析为数值类型:

    Live On C++03

    #include <boost/spirit/include/qi.hpp>
    #include <boost/utility/string_view.hpp> 
    
    int main() {
        namespace qi = boost::spirit::qi;
    
        boost::string_view number_view("12345");
        int dest;
    
        if (qi::parse(
              number_view.begin(), number_view.end(), 
              qi::int_,
              dest)) {
           std::cout << dest << std::endl;
        }
    
        return 0;
    }
    

    12345