Как достичь std :: from_chars () только с c ++ 14 - PullRequest
3 голосов
/ 27 мая 2020

Я пытаюсь преобразовать boost :: string_view в целое число. В этом сообщении обсуждается использование from_chars(), но оно доступно в C ++ 17, и я ищу решение для C ++ 14.

Какой вариант здесь лучше всего?

Ответы [ 2 ]

4 голосов
/ 28 мая 2020

В духе [sic] теперь удаленного ответа @ t-niese я бы предложил подход Spirit. Поскольку 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).

Вы даже можете заставить его анализировать целые числа на нецелые типы, если вы действительно хотите, хотя это не анализировать нецелочисленные форматы, см. Как быстро анализировать разделенные пробелами числа с плавающей запятой в C ++? для этого, если вас интересует больше.

Также см. здесь, чтобы узнать, насколько они эффективны процедуры на практике.

Тестовые примеры

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 В Coliru

ПРИМЕЧАНИЕ ОБНОВЛЕНО для улучшения интерфейса без метания, который обновляет string_view, чтобы отразить, какая часть ввода была использована (см. Ниже в БОНУСНЫЕ ТЕМЫ ). Последние два теста теперь печатают:

"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
    
1 голос
/ 27 мая 2020

В boost вы можете использовать spirit::qi, чтобы преобразовать последовательность, заданную итераторами, в число c тип:

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
...