Kompilieren Sie Zeittext in Zahlenübersetzung (atoi)

8

Ich möchte die Funktion atoi () zur Kompilierungszeit implementieren (in C ++ - Sprache unter Verwendung des C ++ 11- oder C ++ 14-Standards). Es sollte also in der Lage sein, in doppelten Anführungszeichen eingeschlossenen Text als Zahl zu analysieren oder einen Fehler zu melden. Insbesondere ist es Teil eines größeren Systems, das das druckähnliche Format zur Kompilierungszeit analysieren kann. Und ich möchte Formatzeichenfolgen auf Wörter aufteilen und wenn ein bestimmtes Wort durch eine Zahl dargestellt werden kann - geben Sie die Nummer anstelle der Zeichenfolge aus (hinter den Kulissen befindet sich die Serializer-Klasse, die Zahlen effektiver serialisieren kann als die Zeichenfolgen und die mehr ist Wichtig ist, dass der Deserializer dann nicht versuchen sollte, jede Zeichenfolge als Zahl zu analysieren, da alle Zahlen, die innerhalb der Formatzeichenfolge gedruckt werden, immer als Zahlen dargestellt werden, jedoch nicht als Zeichenfolgen.

Wie ich zwei kenne, kann es zwei Ansätze geben, um die Aufgabe zu lösen:

1) unter Verwendung von constexpr-Funktionen;

2) durch Template-Metaprogrammierung.

Welcher Weg kann besser sein? Ich habe den ersten Weg ausprobiert und sehe, dass es auf diese Weise viele Hindernisse gibt: insbesondere wenige Einschränkungen in Bezug auf c ++ 11. Es sieht so aus, als wäre eine zweite vorzuziehen, erfordert jedoch einige Tricks (Sie müssen den C-String teilen, um die Zeichen mithilfe des Operators "" zu trennen, der in gcc ab C ++ 14 und in Clangs ab C ++ 11 unterstützt wird ). Auch eine vollständig auf TMP basierende Lösung ist möglicherweise zu groß und zu verworren.

Unten ist meine Lösung, ich freue mich über einige Vorschläge dazu.

http://coliru.stacked-crooked.com/a/0b8f1fae9d9b714b


#include <stdio.h>

template <typename T> struct Result
{
    T value;
    bool valid;

    constexpr Result(T v) : value(v), valid(true) {}
    constexpr Result() : value(), valid(false) {}
};

template <typename T>
constexpr Result<T> _atoi_oct(const char *s, size_t n, T val, int sign)
{
    return n == 0 ? Result<T>(sign < 0 ? -val : val)
        : *s >= '0' && *s <= '7' 
            ? _atoi_oct(s+1, n-1, val*T(010) + *s - '0', sign)
            : Result<T>();
}

template <typename T>
constexpr Result<T> _atoi_dec(const char *s, size_t n, T val, int sign)
{
    return n == 0 ? Result<T>(sign < 0 ? -val : val)
        : *s >= '0' && *s <= '9'
            ? _atoi_dec(s+1, n-1, val*T(10) + *s - '0', sign)
            : Result<T>();
}

template <typename T>
constexpr Result<T> _atoi_hex(const char *s, size_t n, T val, int sign)
{
    return n == 0 ? Result<T>(sign < 0 ? -val : val)
        : *s >= '0' && *s <= '9'
            ? _atoi_hex(s+1, n-1, val*T(0x10) + *s - '0', sign)
            : *s >= 'a' && *s <= 'f'
                ? _atoi_hex(s+1, n-1, val*T(0x10) + *s - 'a' + 10, sign)
                : *s >= 'A' && *s <= 'F'
                    ? _atoi_hex(s+1, n-1, val*T(0x10) + *s - 'A' + 10, sign)
                    : Result<T>();
}

template <typename T>
constexpr Result<T> _atoi_zero(const char *s, size_t n, int sign = 1)
{
    return n == 0 ? Result<T>()
        : *s >= '0' && *s <= '7'
            ? _atoi_oct(s+1, n-1, T(*s - '0'), sign)
            : *s == 'x' || *s == 'X'
                ? _atoi_hex(s+1, n-1, T(0), sign)
                : Result<T>();
}

template <typename T>
constexpr Result<T> _atoi_sign(const char *s, size_t n, int sign = 1)
{
    return n == 0 ? Result<T>()
        : *s == '0'
            ? _atoi_zero<T>(s+1, n-1, sign)
            : *s > '0' && *s <= '9'
                ? _atoi_dec(s+1, n-1, T(*s - '0'), sign)
                : Result<T>();
}

template <typename T>
constexpr Result<T> _atoi_space(const char *s, size_t n)
{
    return n == 0 ? Result<T>()
        : (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r' || *s == '\v')
            ? _atoi_space<T>(s+1, n-1)
            : *s == '-'
                ? _atoi_sign<T>(s+1, n-1, -1)
                : *s == '+'
                    ? _atoi_sign<T>(s+1, n-1)
                    : *s == '0'
                        ? _atoi_zero<T>(s+1, n-1)
                        : _atoi_dec(s, n, T(0), 1);
}

template <size_t N> void pstr(const char (&s)[N])
{
    printf("s '%.*s'\n", int(N-1), s);
}

template <typename Str>
__attribute__((always_inline))
void _atoi(Str s)
{
    constexpr auto result = _atoi_space<long>(s.cstr(), sizeof(s.cstr())-1);
    if (result.valid)
        printf("i %ld\n", result.value);
    else
        pstr(reinterpret_cast<const char (&)[sizeof(s.cstr())]>(s.cstr()));
}

#define atoi(STR) _atoi([]() { \
                        struct S { \
                            static constexpr const char (&cstr())[sizeof(STR)] { return STR; } \
                        }; \
                        return S();  \
                    }())

int main()
{
    atoi("42");
    atoi("-1");
    atoi("+1");
    atoi("010");
    atoi("-0x10");
    atoi("--1");
    atoi("x");
    atoi("3x");
    return 0;   
}

Grundsätzlich möchte ich fragen, wie ich zur Kompilierungszeit eine Zahl (wie "42") in doppelte Anführungszeichen in den Wert des Integraltyps umwandeln kann . Meine Lösung sieht zu umständlich aus.

fk0
quelle
Eigentlich habe ich so etwas gesucht. Es kann seine Verwendung mit Hashing-Strings zur Kompilierungszeit oder etw haben. Ein anderer Ansatz könnte die Compiler-API-Programmierung sein, aber das wäre compilerspezifisch.
Marcin Poloczek
C ++ 17 ist keine Option, oder?
Timo
Also, was ist los mit deiner Lösung? Und was willst du genau wissen? Besser in welchem ​​Sinne?
Florestan
@MarcinPoloczek, wie Sie erwähnt haben, String-Hashing zur Kompilierungszeit: Vielleicht gefällt Ihnen dieser Beitrag, den ich vor einiger Zeit hier auf SO geschrieben habe: stackoverflow.com/a/47081012/8494588
florestan
Aktualisierte Version mit Fehlerprüfung: coliru.stacked-crooked.com/a/9f67878a7be4310c
fk0

Antworten:

0

Mit C ++ 14 können wir das Makro und einige ternäre Operatoren entfernen. Hier ist, wie ich es machen würde, der Code sollte selbsterklärend sein (ich habe auch einige Kommentare hinzugefügt). Der folgende Code kann auch hier (mit einigen Beispielen) zum Compiler-Vergleich gefunden werden.

#include <cstdint>
#include <iostream>

template <typename T>
struct Result
{
    T value{};
    bool valid = false;

    constexpr explicit Result(T v) : value(v), valid(true) {}
    constexpr Result() = default;
};

// converts upper case chars to lower case
constexpr char to_lower(char c)
{
    return c >= 'A' && c <= 'Z'
        ? c - 'A' + 'a'
        : c;
}

// converts a digit char to its numeric value (eg. 'F' -> 15)
constexpr int to_digit(char c)
{
    c = to_lower(c);
    return c >= 'a'
        ? c - 'a' + 10
        : c - '0';
}

// checks whether the given digit fits in the given base (eg. 'A' in 16 (hex) -> true, but '9' in 8 (oct) -> false)
constexpr bool is_digit(char c, int base)
{
    int digit = to_digit(c);
    return 0 <= digit && digit < base;
}

namespace detail
{
    // returns true if c is a sign character (+ or -), sign will hold a valid factor (1 or -1) regardless of the return value
    constexpr bool get_sign(char c, int& sign)
    {
        if (c == '-')
        {
            sign = -1;
            return true;
        }
        else
        {
            sign = 1;
            return c == '+';
        }
    }

    // adds a digit to the right side of the a number
    template <typename T>
    constexpr T append_digit(T value, int base, int digit)
    {
        return value * base + digit;
    }

    // create the actual number from the given string
    template <typename T>
    constexpr T construct_integral(const char* str, std::size_t size, int base)
    {
        T value = 0;
        for (std::size_t i = 0; i < size; i++)        
            value = append_digit(value, base, to_digit(str[i]));

        return value;
    }

    // how many chars are necessary to specify the base (ex. hex -> 0x -> 2) 
    constexpr std::size_t get_base_offset(int base)
    {
        if (base == 8) return 1;
        if (base == 16) return 2;
        return 0;
    }

    // gets the base value according to the number prefix (eg. 0x -> 16 (hex))
    constexpr int get_base(const char* str, std::size_t size)
    {
        return str[0] == '0'
            ? size > 2 && to_lower(str[1]) == 'x'
                ? 16
                : 8
            : 10;
    }

    // checks whether all digits in the string can fit in the given base
    constexpr bool verify_base(const char* str, std::size_t size, int base)
    {
        for (std::size_t i = 0; i < size; i++)
            if (!is_digit(str[i], base))
                return false;

        return true;
    }
}

template <typename T = int>
constexpr Result<T> to_integral(const char* str, std::size_t size)
{
    using namespace detail;

    // remove the sign from the string
    auto sign = 0;    
    if (get_sign(str[0], sign)) 
    {
        ++str;
        --size;
    }

    // get the base and remove its prefix from the string
    auto base = get_base(str, size);
    auto offset = get_base_offset(base);
    str += offset;
    size -= offset;

    // check if the string holds a valid number with respect to its base
    if (!verify_base(str, size, base))
        return {};

    // create the number and apply the sign
    auto unsigned_value = construct_integral<T>(str, size, base);
    return Result<T>(unsigned_value * sign);
}

template <typename T = int, std::size_t N>
constexpr Result<T> to_integral(const char(&str)[N])
{
    static_assert(N > 1, "Empty strings are not allowed");
    return to_integral<T>(str, N - 1);
}

C ++ 17 könnte die Codemenge durch Verwendung noch weiter reduzieren std::string_view. Ihr Result<T>könnte auch durch ersetzt werden std::optional.

Timo
quelle