ちょっと便利な文字列変換の紹介
今回紹介するのは、文字列を数字に変換する関数です!
「そんなもん誰でも知っとるわい!std::stoi
でしょ!」と思っているあなた!
私も昔からstd::stoi
を使っていますが、他にもstd::atoi
, std::stringstream
, sscanf
などという選択肢もあります。
文字列に数字しか入っていないという保証がある場合は、正直どちらを使っても問題ないです。
std::string str = "12345";
int number = 0;
number = std::stoi(str);
std::atoi(str.c_str());
std::stringstream strm(str);
strm >> number;
sscanf(str.c_str(), "%d", &number);
使い方がそれぞれ違うため、好きな方を使えばいいです。
- 私は可能な限りモダン C++ のスタイルで行きたい派なので、
sscanf
は個人的に論外です。 -
std::atoi
はconst char*
を渡さないといけないため、少し使いづらいです。 -
std::stringstream
はファイルやユーザーインプットからの読み込みの場合は使うと思いますが、特定の文字列を変換するためだけには少しやりすぎな気がします。 - 結局
std::stoi
になるのですが、実はstd::stoi
が使えない環境も存在します。これについては後ほど説明します。
さっきの例は数字しか入っていない文字列を変換しましたが、数字でない文字が入っている場合はどうしますか?
std::atoi
の場合は、文字列が数字でない場合は0
を返します。そうすると、「インプットがたまたま "0" という文字列だった場合」と区別できないため、お勧めできないです。
それに対して、std::stoi
では以下のように対応すると思います。
std::size_t pos{};
try
{
number = std::stoi(str, &pos);
std::cout << "number: " << number << std::endl;
}
catch (std::invalid_argument const& ex)
{
std::cout << "std::invalid_argument::what(): " << ex.what() << std::endl;
}
catch (std::out_of_range const& ex)
{
std::cout << "std::out_of_range::what(): " << ex.what() << std::endl;
}
if (pos != str.length())
{
std::cout << "conversion was partly successful" << std::endl;
}
else
{
std::cout << "conversion was successful" << std::endl;
}
文字列に数字でない文字しか入っていないせいで変換に失敗したら、std::invalid_argument
という例外が投げられます。
数字への変換が成功したがint
型に収まらないぐらい大きな数字だった場合は、std::out_of_range
という例外が投げられます。
そういう時はstd::stol
というlong
型版かstd::stoll
というlong long
型版を使うといいです。
ただ、-fno-exceptions
という例外が投げられないビルド設定が採用されている環境も存在します。そういう環境ではstd::stoi
の例外に対応できないため、文字列の変換が失敗しかねない場合は使えません。
そこで今回紹介したい関数std::from_chars
の出番です!
const auto last = str.data() + str.size();
auto [ptr, ec] = std::from_chars(str.data(), last, number);
if (ec == std::errc())
std::cout << "number: " << number << std::endl;
else if (ec == std::errc::invalid_argument)
std::cout << "This is not a number." << std::endl;
else if (ec == std::errc::result_out_of_range)
std::cout << "This number is larger than an int." << std::endl;
else if (ptr != last)
std::cout << "conversion was partly successful" << std::endl;
else
std::cout << "conversion was successful" << std::endl;
数字でない文字ばっかり入っているせいで変換に失敗したら、ec
にstd::errc::invalid_argument
というエラーが返されます。
数字への変換が成功したがint
型に収まらないぐらい大きな数字だったら、std::errc::result_out_of_range
というエラーが返されます。
基本的にstd::stoi
のような使い方ですが、個人的に少し使いづらいと思うのは、number
が出力引数になっていることです。本当はptr
とec
と一緒に戻り値として返して欲しいところです。
引数がconst char*
になっているのも少し微妙です。でも例外に対応できない環境でも使えるのでお勧めです!
補足説明として少しだけ触れますが、std::stoi
とstd::from_chars
にはbase
という基数が指定できる引数も存在します。どちらもデフォルト値が10進数の10
になっていますが、2
から36
が指定できます。
std::string hex_str = "ff";
int hex_val;
std::from_chars(hex_str.data(), hex_str.data() + hex_str.size(), hex_val, 16);
std::cout << "Parsed hex: " << hex_val << std::endl; // Output: 255
std::string bin_str = "1010";
int bin_val;
std::from_chars(bin_str.data(), bin_str.data() + bin_str.size(), bin_val, 2);
std::cout << "Parsed binary: " << bin_val << std::endl; // Output: 10
std::stoi
の場合は、base
に0
も指定できます。そうすると、std::stoi
が以下のルールで自動的に基数を推測してくれます。
- 文字列が
0
から始まる場合は8進数として解釈される - 文字列が
0x
から始まる場合は16進数として解釈される - それ以外は10進数として解釈される
それに対して、std::from_chars
のbase
に0
を指定すると、ec
にstd::errc::invalid_argument
が返ります。
Discussion