Eu quero implementar a função atoi () em tempo de compilação (na linguagem C ++, usando o padrão C ++ 11 ou C ++ 14). Portanto, ele deve poder analisar o texto entre aspas duplas como número ou relatar um erro. Mais especificamente, é parte de um sistema maior, capaz de analisar o formato semelhante ao printf em tempo de compilação. E eu quero dividir seqüências de caracteres de formato em palavras e se alguma palavra específica pode ser representada por número - número de saída em vez da sequência de caracteres (nos bastidores é a classe do serializador, que pode serializar números com mais eficiência do que as seqüências de caracteres, e que é mais importante, o desserializador não deve tentar analisar cada sequência como um número, porque todos os números impressos dentro da sequência de formato são sempre representados como números, mas não como sequências) ...
Como sei duas, pode haver duas abordagens para resolver a tarefa:
1) usando funções constexpr;
2) por metaprogramação de modelos.
Qual caminho pode ser melhor? Eu tentei pela primeira vez e posso ver que existem muitos obstáculos dessa maneira: especialmente poucas limitações relacionadas ao c ++ 11. Parece que o segundo pode ser preferível, mas exige alguns truques (você precisa dividir a string c para separar caracteres usando o operador "", que é suportado no gcc a partir do c ++ 14 e em clangs a partir do c ++ 11 ) Além disso, a solução completamente baseada em TMP pode ser muito grande e emaranhada.
Abaixo está minha solução, fico feliz em ouvir algumas sugestões sobre isso.
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;
}
Basicamente, quero perguntar, como posso transformar no número de tempo de compilação (como "42") escrito em aspas duplas no valor do tipo integral. Minha solução parece muito complicada.
Respostas:
Com o C ++ 14, podemos nos livrar da macro e de alguns operadores ternários. Aqui está como eu faria isso, o código deve ser autoexplicativo (também adicionei alguns comentários). O código abaixo também pode ser encontrado aqui (com alguns exemplos) para comparação do compilador.
O C ++ 17 poderia reduzir ainda mais a quantidade de código usando
std::string_view
. VocêResult<T>
também pode ser substituído porstd::optional
.fonte