組み込みでも使える、scanf に代わる C++ input.hpp


scanf に似た仕様で文字列から数値へ変換する C++ クラス

概要

C++ では、C 言語で一般的な可変引数を扱う関数は使わない文化があります。
一番の問題は、引数の引渡しはスタックを経由する点、又、スタックに引数が何個格納
されているのか判らない点です、これによりスタックをオーバーロードさせ、システム
に悪影響を与える事が出来てしまいます。

代表的な実装は「printf」関数です。
コンパイラはフォーマット文を解析して、引数の整合性をチェックしますが、完全にはチェックできません。

一方、「printf」は優れた柔軟性をもたらし、文字、数値を扱う事をたやすくします。

boost には、printf の柔軟性と、安全性を考慮した、format.hpp があります。
※「%」オペレーターのオーバーロード機構を利用して、複数の引数を受け取る事ができます。
※boost::format は優れた実装ですが、iostream に依存していて組み込みマイコンでは問題があります。
※iostream を取り込むと、容量が肥大化します。

その為、組み込みマイコン向けの軽量な「format.hpp」を実装しています。

同じように、printf とは逆の動作をする scanf も安全性においては printf と同じ問題を抱えています。

そこで、format.hpp の仕組みをまねて、scanf に代わる input クラスを実装しました。

仕様

  • 基本的な仕様
    2進数、8進数、10進数、16進数、浮動小数点、文字などを受け取る事が出来ます。
  • 名前空間は「utils」です。
  • 文字列の受け取りは、ファンクタを定義して、テンプレートパラメーターとします。
    ※標準的なファンクタ「def_chainp」クラスが定義されており、以下のように
    typedef されています。
    typedef basic_input<def_chainp> input;

※「def_chainp」クラスも、「input.hpp」に含まれています。

標準のファンクタは、標準入力から文字を受け取りますが、キャラクター型ポインター

を定義する事ができ、「sscanf」のように機能します。

組み込みマイコンで使う事を考えて、エラーに関する処理では、「例外」を送出しません。
入力変換時に起こったエラーは、エラー種別として取得する事ができます。

  • 一般的に、例外を使うと多くのメモリを消費します。
  • 例外を使った場合、エラーが発生して、正しい受取先が無い場合、致命的な問題を引き起こします。
  • 複数の変換で、エラーが同時に発生すると、最後のエラーが残ります。
  • 複数の分離キャラクターを扱える正規表現があります。
  • %b ---> 2進の数値
  • %o ---> 8進の数値
  • %d ---> 10進の数値
  • %u ---> 符号無し10進
  • %x ---> 16進の数値
  • %f ---> 浮動小数点数(float、double)
  • %c ---> 1文字のキャラクター
  • %a ---> 自動、2進(bnnn)、8進(onnn)、10進、16進(xnnn)、を判別

※ %c は、半角文字のみ対応

  • %、[、]、などの特殊文字を、分離キャラクターとして使う場合は、「バックスラッシュ」を使う。

使い方

input.hpp をインクルードします。

#include "input.hpp"

※エクスポーネント算術に「std::pow」関数を利用する為、算術ライブラリのリンクが必要です。

※全ての機能を使うのに必要なヘッダーは「input.hpp」のみです。

名前空間は「utils」です。

サンプル

  • 標準入力から、変数「a」に10進数を受け取ります。
    ※標準入力から受け取る場合、入力の終端は、'\n'(改行)とします。
   int a;
   utils::input("%d") % a;
  • 以下のように変換ステートを受け取る事が出来るので、変換に失敗した場合を検出できます。
   int a;
   if((utils::input("%d") % a).state()) {
       // OK
   } else {
       // NG
   }
  • 受け取り時、どんなエラーがあったのかを判別したい場合、以下のようにエラー種別を取得
    する事ができます。
   int a;
   auto err = (utils::input("%d") % a).get_error();
   if(err == utils::input::error::none) {
       // OK
   } else {
       // NG: 「err」種別により、相当する処理
   }
  • エラー種別は enum class で定義されます。
    none,            ///< エラー無し
    cha_sets,        ///< 文字セットの不一致
    partition,       ///< 分離キャラクターの不一致
    input_type,      ///< 無効な入力タイプ
    not_integer,     ///< 整数型の不一致
    different_sign,  ///< 符号の不一致
    sign_type,       ///< 符号無し整数にマイナス符号
    not_float,       ///< 浮動小数点型の不一致
    terminate,       ///< 終端文字の不一致
    overflow,        ///< オーバーフロー    
  • 文字列から受け取る場合(sscanf 相当)は、以下のようにします。
    ※第二引数が省略された場合、上の例のように、標準入力となります。
   int a;
   static const char* inp = { "1234" };
   utils::input("%d", inp) % a;
  • 特殊文字「%, [, ]」を分離キャラクターとして使う場合、「\」(バックスラッシュ)で除外します。 ※「\」を文字列に1文字含める場合、「\」とします。
    static const char* inp = { "123%456[789]5678" };
    int a[4] = { -1 };
    auto err = (input("%d\\%%d\\[%d\\]%d", inp) % a[0] % a[1] % a[2] % a[3]).get_error();
    std::cout << "Test23, Special character separator as '" << inp << "': "
        << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3];
    if(err == input::error::none) {

    }

拡張機能、分離キャラクタ

数値ブロックを分離するキャラクターには、数値(10進の場合 0 から 9)以外を指定が出来ます。
さらに。「[」、「]」で囲まれた任意のキャラクターを指定出来ます。
以下のサンプルでは、分離キャラクターとして、「 」(スペース)、「,」のどちらかを指定出来ます。

    int a = 0;
    uint32_t b = 0;
    int c = 0;
    static const char* inp = { "-99 100,200" };
    auto n = (input("%d[, ]%x[ ,]%d", inp) % a % b % c).num();
    std::cout << "Test05, multi scan for integer: " << a << ", " << b << ", " << c;
    if(n == 3 && a == -99 && b == 0x100 && c == 200) {
        std::cout << "  Pass." << std::endl;
        ++pass;
    } else {
        std::cout << "  Scan NG!" << std::endl;
    }

拡張機能、「%a」オート

接頭子として、「b,0b」二進数、「o,0o」八進数、「x,0x」16進数、「なし」10進数、を受け付ける機能。
※整数のみで、浮動小数点は受け取れません。
※先頭の「0」は省略可能

    static const char* inp = { "100 0x9a 0b1101 0o775" };
    int a[4] = { -1 };
    auto n = (input("%a %a %a %a", inp) % a[0] % a[1] % a[2] % a[3]).num();
    std::cout << "Test10, multi scan for 'auto': "
        << a[0] << ", " << a[1] << ", " << a[2] << ", " << a[3] << " (" << n << ")";
    if(n == 4 && a[0] == 100 && a[1] == 0x9a && a[2] == 0b1101 && a[3] == 0775) {
        std::cout << "  Pass." << std::endl;
        ++pass;
    } else {
        std::cout << "  Scan NG!" << std::endl;
    }

変換エラーが発生した場合:

  • 変換エラーが発生した場合、参照へは、結果を返さず、破棄されます。
    int a = -1;
    input("%d", "1O5") % a;

上記のように、変換に失敗した(文字の O が含まれる)場合は、初期化の値「-1」が保持されます。

オーバーフローエラーの挙動:

  • 浮動小数点の小数点以下は、桁あふれ以降の数値は無視され、オーバーフローエラーは返しません。
  • 実数部では、「utils::input::error::overflow」がセットされます。
    ※オーバーフローエラーの場合、オペレーターで指定された参照へは、オーバーフローする前の値を返します。

プロジェクト(全体テスト)

input クラスは、全体テストと共に提供されます。

make

で全体テストがコンパイルされます。

make run

全体テストが走り、全てのテストが通過すると、正常終了となります。

※テストに失敗すると、-1 を返します。


License

MIT