Шаблон дружественной строки к числовому в C ++

48

В стандартной библиотеке C ++ есть функции для преобразования из строковых в числовые типы:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

но я считаю утомительным использовать их в шаблоне кода. Почему нет шаблонных функций что-то вроде:

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

преобразовать строки в числовые типы?

Я не вижу никаких технических причин, чтобы не иметь их, но, возможно, я что-то упустил. Они могут быть специализированными для вызова базовых именованных функций и использования enable_if/ conceptsдля отключения нечисловых типов.

Есть ли в стандартной библиотеке дружественные к шаблону альтернативы для преобразования строки в числовые типы и наоборот эффективным способом?

Мирча Испас
источник
Отвечает ли это на ваш вопрос? Почему серия `std :: sto` ... не является шаблоном?
Boiethios
1
@Boiethios не совсем - ответы на этот вопрос объясняют причину «почему», но они не приходят с практическими решениями, такими как принятый ответ. Я отредактировал свой вопрос, чтобы попросить альтернативу, чтобы лучше указать, что мне нужно
Мирча Испас

Ответы:

40

Почему нет шаблонных функций что-то вроде:

C ++ 17 имеет такую ​​обобщенную строку для числовой функции, но называется по-другому. Они пошли с std::from_chars, который перегружен для всех числовых типов.

Как видите, первая перегрузка принимает любой целочисленный тип в качестве выходного параметра и присваивает ему значение, если это возможно.

Это можно использовать так:

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
    }
}

Как видите, он может работать в общем контексте.

Гийом Расикот
источник
5
C ++ имеет деструктуризацию сейчас? : o Структурированная обязательная декларация
Александр - Восстановить Монику
1
Конечно! Это даже работает с простыми структурами или, если дать правильный интерфейс, классы тоже.
Гийом Расикот
13

Это не шаблон, и он не работает с локалями, но если это не ограничитель показа, то C ++ 17 уже имеет то, что вы хотите: std::from_chars

Существуют перегрузки для всех целочисленных типов и типов с плавающей запятой, и интерфейс один и тот же, за исключением последних параметров, которые отличаются для целочисленных типов и типов с плавающей запятой соответственно (но если по умолчанию все в порядке, то вам не нужно изменить что угодно). Поскольку эта функция не поддерживает локали, она также довольно быстрая. Он превзойдет любую другую строку в функцию преобразования значений и, как правило, на порядки.

Существует очень хорошее видео CPPCON <charconv>(в котором from_charsживет заголовок ) Стефана Т. Лававея, о его использовании и производительности можно посмотреть здесь: https://www.youtube.com/watch?v=4P_kbF0EbZM

NathanOliver
источник
1
@NathanOliver: stoiи его друзья (преобразования, упомянутые в вопросе) также не работают с локалями, так что это не шоу-стоппер.
Пит Беккер
9

Вы не получите много, потому что в выражении, как

int x = sto("1");

Не существует (простого) способа определить требуемый тип для параметра шаблона. Вы должны написать

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

что в некоторой степени противоречит цели предоставления универсальной функции. С другой стороны,

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

было бы полезно, как вы поняли. В C ++ 17 есть std::from_chars, что делает более-менее точно (это не шаблон, а набор перегрузок, и он использует указатели на символы вместо строки, но это лишь незначительные детали).

PS Нет простого способа вывести нужный тип в приведенном выше выражении, но есть способ. Я не думаю, что в основе вашего вопроса была именно та подпись, о которой вы просили, и я не думаю, что следующее является хорошим способом для ее реализации, но я знал, что есть способ сделать вышеупомянутую int x = sto("1");компиляцию, и мне было любопытно увидеть ее в действии.

#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;
}

Это работает как задумано, но имеет серьезные недостатки, возможно, самое главное, что оно позволяет писать auto x = sto(s);, то есть его легко использовать неправильно.

idclev 463035818
источник
Я думаю, что полагаться на неявное преобразование здесь - хорошая идея. Попытка отключить авто является проблемой, хотя. Как правило, я видел это, помещая частную константную ссылку в класс, который инициализируется только допустимыми методами. Я не понимаю, как можно было бы использовать это здесь, потому что нам нужно как-то сконструировать целый объект-конвертер, прежде чем продолжить. Хммм ....
bremen_matt
Я могу видеть значение, несмотря на необнаруженный параметр типа - как говорится в вопросе, мотивация заключается в возможности использовать его из кода шаблона, когда вы конвертируете в тип, который варьируется между экземплярами.
Тоби Спейт
В чем основная проблема auto x = sto(s)? Эта конкретная реализация ломается, потому что converter::xэто ссылка, которая выходит за рамки, но это поправимо. Просто удалите ссылку и положитесь на std::stringсемантику перемещения.
MSalters
@MSalters да, это была ссылка, которую я считал проблематичной, но вы правы, нет необходимости использовать ссылку. Что на самом деле меня больше беспокоит, так это то, что это функция, но реальная функциональность есть converter, и я не уверен, что использование оператора преобразования шаблонов было лучшим выбором, вещи, которые можно исправить. Возможно это не так плохо, как я первоначально думал
idclev 463035818
Я не думаю, что здесь есть какая-либо проблема с константной ссылкой. Насколько я понимаю, ссылка на const сохранит время жизни строки до тех пор, пока не будет уничтожен конвертер ( herbutter.com/2008/01/01/… )
bremen_matt
5

Решение, совместимое со всеми (даже более старыми компиляторами C ++, такими как C ++ - 98), заключается в использовании boost :: lexical_cast, который является шаблоном для преобразования между числовым и строковым типами обоими способами.

Пример:

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

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

Лукаш Śлусарчик
источник
3

В старых версиях C ++ stringstream - ваш друг. Если я правильно понимаю, то вам может быть интересно следующее. Это 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;
}

Этот метод работает в C ++ 11 и является довольно общим. По моему опыту, этот метод является надежным, но не самым эффективным.

bremen_matt
источник
Да, это то, что я использовал, но производительность ниже названных функций, что иногда нежелательно
Мирча Испас