LoginSignup
10
8

More than 5 years have passed since last update.

C++で再帰しつつnoexceptな数値入力関数を作る

Last updated at Posted at 2016-01-22

はじめに

C言語で安全に標準入力から数値を習得
というものを書いたがC++ではどうか見ていく。

幸いなことにCと違いC++の標準入出力は優秀である。

C++での数値入力の実にありきたりな例
int input_num;
std::cin >> input_num;

Cみたいに標準入出力がラスボス、なんてことはない。

しかしながら、クラスについての理解、フラグについての理解はやはり必要になってしまう。

C言語で安全に標準入力から数値を習得で紹介したエラー時は再帰する、と言うのは実は初心者にもわかりやすいのではないのか?と思うのでそれを書いてみる。もちろん例外なんていう初心者にはよくわからないものは意識せず済むよう、noexcept指定をつける。

注意点

arithmeticに限定

nc32177.png

ありすー・・・じゃない、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でも動くぞ・・・。

10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8