はじめに
C言語で安全に標準入力から数値を習得
というものを書いたがC++ではどうか見ていく。
幸いなことにCと違いC++の標準入出力は優秀である。
int input_num;
std::cin >> input_num;
Cみたいに標準入出力がラスボス、なんてことはない。
しかしながら、クラスについての理解、フラグについての理解はやはり必要になってしまう。
C言語で安全に標準入力から数値を習得で紹介したエラー時は再帰する、と言うのは実は初心者にもわかりやすいのではないのか?と思うのでそれを書いてみる。もちろん例外なんていう初心者にはよくわからないものは意識せず済むよう、noexcept
指定をつける。
注意点
arithmeticに限定
ありすー・・・じゃない、arithmeticではないものに対するoperator>>
が呼ばれないよう、TMPで弾く必要がある。もちろんSFINAEを使う。やり方は
std::enable_ifを使ってオーバーロードする時、enablerを使う?
のstd::nullptr_t
型を使うものを使う。
int8_t
/uint8_t
型のために
またint8_t
/uint8_t
型は一般にchar
/unsigned char
にtypedefされており、operator>>
は一文字読み取るという動作になるので、一度大きな型で受けて範囲チェックした後キャストする必要がある。
char
/singed shar
/unsigend char
型はsizeof
演算子で1になることを利用して書けばいいが、今のC++には**static_if
のような便利な機能は無いのでTMPする必要がある**。
この際std::conditional
を使うと非常に読みにくくなるので、
メモ:std::conditionalでif~else if~elseみたいなことをしようとすると見づらい
のやり方を使う。
成果物
#include <iostream>
#include <exception>
#include <stdexcept>
#include <type_traits>
#include <limits>
/**
@brief \~japanese 複数条件のあるTMP用if
\~english multi-conditional if struct for TMP.
\~japanese std::enable_ifと組み合わせて使います。
\~english This class is used in conjunction with std::enable_if.
\~
@code
template<typename T>
using bar = first_enabled_t<
std::enbale_if<cond1, type1>,
std::enbale_if<cond2, type2>,
std::enbale_if<cond3, type3>,
default_type
>;
@endcode
*/
template<typename ...Args>
struct first_enabled {};
template<typename T, typename ...Args>
struct first_enabled<std::enable_if<true, T>, Args...> { using type = T; };
template<typename T, typename ...Args>
struct first_enabled<std::enable_if<false, T>, Args...> : first_enabled<Args...> {};
template<typename T, typename ...Args>
struct first_enabled<T, Args...> { using type = T; };
template<typename ...Args>
using first_enabled_t = typename first_enabled<Args...>::type;
//! for int8_t/uint8_t
template<typename T, std::enable_if_t<std::is_arithmetic<T>::value, std::nullptr_t> = nullptr>
using arithmetic_t = first_enabled_t <
std::enable_if<1 != sizeof(T), T>,
std::enable_if<std::is_signed<T>::value, int>,
unsigned int
>;
template<typename T_> using limit = std::numeric_limits<T_>;//create new type. C++11:alias declaration
/**
* @brief 標準入力から入力を受ける
* @details [long description]
*
* @param echo_str 入力を受ける前に表示する文字列。表示しない場合はnullptrか空白文字のみで構成された文字列へのポインタを渡す
* @param max 入力値を制限する。最大値を指定
* @param min 入力値を制限する。最小値を指定
* @return 入力した数字
* @exception none
*/
template<typename T, std::enable_if_t<std::is_arithmetic<T>::value, std::nullptr_t> = nullptr>//Tが整数か浮動小数点型でないならばコンパイルエラーを出す
T input(const char* echo_str, const T max = limit<T>::max(), const T min = limit<T>::lowest()) noexcept {
arithmetic_t<T> buf;
try {
std::cin.exceptions(std::ios::failbit | std::ios::badbit);
if (nullptr != echo_str && '\0' != echo_str[0]) std::cout << echo_str << std::endl;//文字列が空じゃなければ出力
std::cin >> buf;//入力を受ける
if (max < buf || buf < min) throw std::out_of_range("input is iligal");//範囲チェック
}
catch (std::exception& er) {
std::cerr << er.what() << std::endl;//エラーメッセージ表示
return input("再入力してください。", max, min);//エラー時は再帰する方向で
}
return static_cast<T>(buf);
}
使い方
int main(){
const auto input1 = input("数字を1~50の間で入力してください", 50, 1);//input1はint型
const auto input2 = input("数字を1~50の間で入力してください", 50U, 1U);//input2はunsigned int型
const auto input3 = input<unsigned long long>("数字を1~50の間で入力してください", 1200, 3);//input3はunsigned long long型
const auto input4 = input<unsigned short>("数字を入力してください");//input4はunsigned short型
return 0;
}
結論
内部実装は例外を乱用したものになり、若干重いかも知れないが、そもそも標準入出力に速度を求めてもしかたがないのでこれでいいとする。
追記
よくよく見たらこいつboolでも動くぞ・・・。