constexpr な strtol 関数を書いた

書きました。
constexpr だと代入が出来ないので、第二引数が NULL、もしくは引数が文字列と基数の場合のみ constexpr な値を返します。

[ソース]

#include <cstdlib>
#include <climits>

namespace ce{

// 暫定版
// 英字の連続性は保証されていないので、ぐぬぬ。
template<typename Char>
constexpr bool
isdigit(Char c){
    return Char('0') <= c && c <= Char('9');
}

template<typename Char>
constexpr bool
isupper(Char c){
    return Char('A') <= c && c <= Char('Z');
}

template<typename Char>
constexpr bool
islower(Char c){
    return Char('a') <= c && c <= Char('z');
}

template<typename Char>
constexpr long
ctoi(Char c){
    return isdigit(c) ? c - Char('0')
         : islower(c) ? 10 + (c - Char('a'))
         : isupper(c) ? 10 + (c - Char('A'))
         : Char(-1);
}

template<typename Char>
constexpr long
strtol_impl(Char const* s, int radix, int sign, long tmp){
    return ctoi(s[0]) == -1 || ctoi(s[0]) >= radix
            ? tmp
        : tmp > (LONG_MAX - ctoi(s[0]) - sign) / radix
            ? (sign ? LONG_MIN : LONG_MAX)
        : s[1] == Char('\0')
            ? tmp * radix + ctoi(s[0])
        : strtol_impl(s + 1, radix, sign, tmp * radix + ctoi(s[0]));
}

template<typename Char>
constexpr long
strtol_prefix(Char const* s, int radix, int sign){
    return s[0] != Char('0')
                ? strtol_impl(s, radix == 0 ? 10 : radix, sign, 0)
         : s[1] == Char('x') || s[1] == Char('X')
                ? strtol_impl(s + 2, radix == 0 ? 16 : radix, sign, 0)
         : strtol_impl(s + 1, radix == 0 ? 8 : radix, sign, 0);
}

template<typename Char>
constexpr long
strtol_sign(Char const* s, int radix){
    return s[0] == Char('-') ? -strtol_prefix(s+1, radix, 1)
         : s[0] == Char('+') ?  strtol_prefix(s+1, radix, 0)
         : strtol_prefix(s, radix, 0);
}

template<typename Char>
constexpr long
strtol_removespace(Char const* s, int radix){
    return s[0] == Char(' ') ? strtol_removespace(s+1, radix)
         : strtol_sign(s, radix);
}

template<typename Char>
constexpr long
strtol(Char const* s, int radix){
    return strtol_removespace(s, radix);
}

template<typename Char>
constexpr long
strtol(Char const* s, char** endptr, int radix){
    return endptr == NULL
        ? strtol(s, radix)
        : std::strtol(s, endptr, radix);
}

}  // namespace ce

#include <iostream>

int
main(){
/*
    static_assert(ce::isdigit('0'), "");
    static_assert(ce::isdigit('9'), "");
    static_assert(ce::isdigit('5'), "");
    static_assert(!ce::isdigit('a'), "");

    static_assert(ce::isupper('V'), "");
    static_assert(!ce::isupper('a'), "");
    static_assert(!ce::isupper('0'), "");
    
    static_assert(ce::strtol("52", 0)   == 52, "");
    static_assert(ce::strtol("0377", 0) == 255, "");
    static_assert(ce::strtol("0xff", 0) == 255, "");
    static_assert(ce::strtol("0xFF", 0) == 255, "");
*/
    static_assert(ce::strtol("52", 10)  == 52, "");
    static_assert(ce::strtol("377", 8)  == 255, "");
    static_assert(ce::strtol("1001", 2) == 9, "");
    static_assert(ce::strtol("ff", 16)  == 255, "");
    
    static_assert(ce::strtol("  +42", 10) ==  42, "");
    static_assert(ce::strtol("  -42", 10) == -42, "");

    static_assert(ce::strtol("-10hoge", 0) == -10, "");
    static_assert(ce::strtol("f3fa", 0)    == 0, "");

    static_assert(ce::strtol("+0xff", 16) == 255, "");

    static_assert(ce::strtol("3087663490", 10) == LONG_MAX, "");

    static_assert(ce::strtol("1234", 5) == 194, "");
    static_assert(ce::strtol("ABCDEF", 0) == 0, "");
    
    // std::strtol が呼ばれる
    char str[] = "42fc";
    char* error = NULL;
    std::cout << ce::strtol(str, &error, 10) << std::endl;
    std::cout << error << std::endl;

    return 0;
}

[出力]

42
fc


おお、だいぶ酷いソースに…もっと綺麗にしたい。
本当は英字の連続性は規格で保障されていないので、そこら辺の処理をどうにかしないとダメなんですが、今回はあまり深く考えないで書いて見ました。
あとテストが適当なのでバグがあるかも。


しかし、constexpr だとソースを綺麗にするのが大変ですね。
ローカル変数が使えないので、変な関数が増えていく…。
あと条件演算子も慣れてないと見づらい。
まぁ constexpr な関数を作っている時は縛りプレイみたいな感じで結構楽しかったりするんですが。

[コンパイラ]

  • g++ (GCC) 4.7.0 20110924 (experimental)