Biên dịch văn bản thời gian để dịch số (atoi)


8

Tôi muốn thực hiện hàm atoi () tại thời gian biên dịch (bằng ngôn ngữ C ++, bằng cách sử dụng tiêu chuẩn C ++ 11 hoặc C ++ 14). Vì vậy, nó sẽ có thể phân tích văn bản kèm theo dấu ngoặc kép dưới dạng số hoặc sửa lỗi. Cụ thể hơn, nó là một phần của hệ thống lớn hơn, có thể phân tích định dạng giống như printf tại thời gian biên dịch. Và tôi muốn phân chia các chuỗi định dạng trên các từ và nếu một số từ cụ thể có thể được biểu thị bằng số - số đầu ra thay vì chuỗi (phía sau cảnh là lớp serializer, có thể tuần tự hóa các số hiệu quả hơn so với các chuỗi và nhiều hơn quan trọng, trình giải mã sau đó không nên cố phân tích từng chuỗi dưới dạng số, vì tất cả các chữ số được in bên trong chuỗi định dạng luôn được biểu diễn dưới dạng số, nhưng không phải là chuỗi) ...

Theo tôi biết hai có thể có hai cách tiếp cận để giải quyết nhiệm vụ:

1) bằng cách sử dụng các hàm constexpr;

2) bằng siêu lập trình mẫu.

Cách nào có thể tốt hơn? Tôi đã thử cách đầu tiên và tôi có thể thấy có nhiều trở ngại theo cách này: đặc biệt là một vài hạn chế liên quan đến c ++ 11. Có vẻ như thứ hai có thể thích hợp hơn, nhưng nó đòi hỏi một số thủ thuật (bạn cần tách chuỗi c để tách các ký tự bằng cách sử dụng toán tử "", được hỗ trợ trong gcc bắt đầu từ c ++ 14 và trong tiếng kêu bắt đầu từ c ++ 11 ). Ngoài ra giải pháp hoàn toàn dựa trên TMP có thể quá lớn và quá rối.

Dưới đây là giải pháp của tôi, tôi rất vui khi nghe một số gợi ý về nó.

http://coliru.stacked-crooking.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;   
}

Về cơ bản tôi muốn hỏi, làm thế nào tôi có thể chuyển đổi theo số thời gian biên dịch (như "42") được viết bằng dấu ngoặc kép trong giá trị của loại tích phân. Giải pháp của tôi trông quá cồng kềnh.


Thật ra tôi đã tìm kiếm thứ gì đó như thế này. Nó có thể có công dụng với các chuỗi băm tại thời gian biên dịch hoặc sth. Ngoài ra một cách tiếp cận khác có thể là lập trình api trình biên dịch nhưng đó sẽ là trình biên dịch cụ thể.
Marcin Poloczek

C ++ 17 không phải là một lựa chọn phải không?
Timo

Vì vậy, những gì sai với giải pháp của bạn? Và những gì bạn muốn biết chính xác? Tốt hơn theo nghĩa nào?
florestan

@MarcinPoloczek như bạn đã đề cập băm chuỗi thời gian biên dịch: có lẽ bạn thích bài đăng này tôi đã viết một thời gian trước đây trên SO: stackoverflow.com/a/47081012/8494588
florestan

Phiên bản cập nhật, với kiểm tra lỗi: coliru.stacked-crooking.com/a/9f67878a7be4310c
fk0

Câu trả lời:


0

Với C ++ 14, chúng ta có thể thoát khỏi macro và một số toán tử ternary. Đây là cách tôi sẽ làm điều đó, mã nên tự lập kế hoạch (tôi cũng đã thêm một số ý kiến). Mã dưới đây cũng có thể được tìm thấy ở đây (với một số ví dụ) để so sánh trình biên dịch.

#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 có thể giảm số lượng mã hơn nữa bằng cách sử dụng std::string_view. Của bạn Result<T>cũng có thể được thay thế bởi std::optional.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.