В духе [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