Sequência amigável de modelo para numérica em C ++

48

Na biblioteca padrão C ++, existem funções para converter de string para tipos numéricos:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

mas acho tedioso usá-los no código do modelo. Por que não há funções de modelo, algo como:

template<typename T>
T sto(...)

converter seqüências de caracteres para tipos numéricos?

Não vejo nenhuma razão técnica para não tê-los, mas talvez esteja perdendo alguma coisa. Eles podem ser especializados para chamar as funções nomeadas subjacentes e usar enable_if/ conceptspara desativar tipos não numéricos.

Existem alternativas compatíveis com modelos na biblioteca padrão para converter seqüências de caracteres em tipos numéricos e o contrário de maneira eficiente?

Mircea Ispas
fonte
Isso responde sua pergunta? Por que a série `std :: sto` ... não é um modelo?
Boiethios
11
@Boiethios, na verdade - as respostas dessa pergunta explicam a lógica por trás do "porquê", mas elas não vêm com soluções práticas, como a resposta aceita. Eu editei minha pergunta para pedir uma alternativa para indicar melhor o que eu preciso
Mircea Ispas 24/02

Respostas:

40

Por que não há funções de modelo, algo como:

O C ++ 17 possui uma string genérica para função numérica, mas com um nome diferente. Eles foram com std::from_chars, que está sobrecarregado para todos os tipos numéricos.

Como você pode ver, a primeira sobrecarga está tomando qualquer tipo inteiro como parâmetro de saída e atribuirá o valor a ele, se possível.

Pode ser usado assim:

template<typename Numeric>
void stuff(std::string_view s) {
    auto value = Numeric{};

    auto [ptr, error] = std::from_chars(s.data(), s.data() + s.size(), value);

    if (error) {
        // error with the conversion
    } else {
        // conversion successful, do stuff with value
    }
}

Como você pode ver, ele pode funcionar em contexto genérico.

Guillaume Racicot
fonte
5
C ++ está destruindo agora? : o Declaração vinculativa estruturada
Alexander - Restabelecer Monica
11
Claro! Até funciona com estruturas simples ou, se for dada a interface correta, também com classes.
Guillaume Racicot 20/02
13

Não é um modelo e não funciona com localidades, mas se isso não for um impedimento de exibição, o C ++ 17 já terá o que você deseja: std::from_chars

Existem sobrecargas para todos os tipos de número inteiro e ponto flutuante e a interface é a mesma, exceto para os últimos parâmetros que são diferentes para os tipos inteiro e de ponto flutuante, respectivamente (mas se o padrão for bom, você não precisará mudar qualquer coisa). Como essa não é uma função com reconhecimento de localidade, também é bastante rápida. Ele irá bater em qualquer outra string para avaliar a função de conversão e geralmente é por ordens de magnitude.

Há um vídeo muito bom do CPPCON sobre <charconv>(o cabeçalho from_charsmora) de Stephan T. Lavavej, que você pode assistir sobre seu uso e desempenho aqui: https://www.youtube.com/watch?v=4P_kbF0EbZM

NathanOliver
fonte
11
@ NathanOliver: stoie seus amigos (as conversões mencionadas na pergunta) também não funcionam com localidades, de modo que não é um impedimento.
Pete Becker
9

Você não ganha muito porque em uma expressão como

int x = sto("1");

Não existe uma maneira (fácil) de deduzir o tipo desejado para o parâmetro do modelo. Você teria que escrever

int x = sto<int>("1");

que, até certo ponto, anula o objetivo de fornecer uma função genérica. Por outro lado, um

template<typename T>
void sto(std::string x,T& t);

seria de bom uso, como você percebeu. No C ++ 17, existe std::from_chars, o que faz mais ou menos exatamente isso (não é um modelo, mas um conjunto de sobrecargas e leva ponteiros para caracteres em vez de uma string, mas são apenas pequenos detalhes).

PS Não há uma maneira fácil de deduzir o tipo desejado na expressão acima, mas existe uma maneira. Não acho que o núcleo da sua pergunta tenha sido exatamente a assinatura solicitada e não acho que seja uma boa maneira de implementá-la, mas sabia que havia uma maneira de fazer a int x = sto("1");compilação acima e fiquei curioso para vê-la. em ação.

#include <iostream>
#include <string>

struct converter {
    const std::string& x;
    template <typename T> operator T() { return 0;}
};

template <> converter::operator int() { return stoi(x); }
template <> converter::operator double() { return stod(x); }
converter sto(const std::string& x) { return {x}; }

int main() {
    std::string s{"1.23"};
    int x = sto(s);
    double y = sto(s);
    std::cout << x << " " << y;
}

Isso funciona como planejado, mas tem desvantagens graves, talvez o mais importante seja que ele permite escrever auto x = sto(s);, ou seja, é fácil usar errado.

idclev 463035818
fonte
Eu acho que confiar na conversão implícita aqui é uma boa ideia. Tentar desativar o auto é um problema. Normalmente, eu vi isso feito colocando uma referência const particular em uma classe que é inicializada apenas por métodos válidos. Não consigo ver como se aproveitaria isso aqui, porque precisamos construir um objeto conversor inteiro antes de prosseguir. Hummm ....
bremen_matt 20/02
Consigo ver valor, apesar do parâmetro do tipo não deduzido - como a pergunta diz, a motivação é poder usar de dentro do código do modelo, onde você está convertendo para um tipo que varia entre instanciações.
Toby Speight
Qual é o problema fundamental auto x = sto(s)? Essa implementação específica converter::xé interrompida porque é uma referência que sai do escopo, mas é corrigível. Apenas remova a referência e conte com std::stringa semântica de movimentação.
MSalters 20/02
@ MSalters sim, foi a referência que achei problemática, mas você está certo, não há necessidade de usar uma referência. O que realmente me perturba mais é que parece ser uma função, mas a funcionalidade real está dentro converter, também não tenho certeza se o uso de um operador de conversão de modelo foi a melhor escolha, coisas que poderiam ser corrigidas. Talvez não seja tão ruim como eu pensava inicialmente
idclev 463035818 20/02
Eu não acho que haja algum problema com a referência const aqui. Meu entendimento é que a referência const preservará a vida útil da string até que o conversor seja destruído ( herbsutter.com/2008/01/01/… )
bremen_matt
5

A solução compatível com todos (inclusive os compiladores C ++ mais antigos, como o C ++ - 98) é usar o boost :: lexical_cast, que é um modelo para converter entre os tipos numéricos e de string de ambos os modos.

Exemplo:

short myInt = boost::lexical_cast<short>(*argv);
std::string backToString = boost::lexical_cast<std::string>(myInt);

Consulte: https://www.boost.org/doc/libs/1_42_0/libs/conversion/lexical_cast.htm

Łukasz Ślusarczyk
fonte
3

Nas versões mais antigas do C ++, o stringstream é seu amigo. Se eu entendi corretamente, o seguinte pode ser interessante para você. É C ++ 11.

https://wandbox.org/permlink/nUNiUwWWTr7a0NXM

#include <sstream>
#include <string>
#include <iostream>

template<typename T, typename String>
T sto(const String & str) {
    T val;
    std::stringstream ss(str);
    ss >> val;
    return val;
}

template<typename T, typename String>
void sto(const String & str, T & val) {
    std::stringstream ss(str);
    ss >> val;
}

int main() {   
    std::cout << sto<float>("1.1") << ", " << sto<int>(std::string{"2"});

    // An alternative version that infers the type 
    double d;
    sto("3.3", d);
    std::cout << ", " << d;
}

Este método funciona em C ++ 11 e é bastante geral. Na minha experiência, esse método é robusto, mas não o mais eficiente.

bremen_matt
fonte
Sim, é isso que eu usei, mas o desempenho está abaixo do nome de funções que às vezes não são desejadas
Mircea Ispas