Друкований рядок шаблону до числового в C ++


48

У стандартній бібліотеці C ++ є функції для перетворення з рядкових в числові типи:

stoi
stol
stoll
stoul
stoull
stof
stod
stold

але мені здається нудним використовувати їх у коді шаблону. Чому немає функцій шаблону на кшталт:

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

конвертувати рядки в числові типи?

Я не бачу жодної технічної причини, щоб їх не було, але, можливо, мені щось не вистачає. Вони можуть бути спеціалізованими для виклику основних названих функцій та використання enable_if/ conceptsвідключення нечислових типів.

Чи є в стандартній бібліотеці якісь зручні альтернативи шаблону для ефективного перетворення рядка в числові типи та навпаки?


Чи відповідає це на ваше запитання? Чому серія `std :: sto` ... не є шаблоном?
Boiethios

1
@Boiethios насправді - відповіді на це питання пояснюють обґрунтування "чому", але вони не мають практичних рішень, як прийнята відповідь. Я відредагував своє запитання, щоб попросити альтернативи, щоб краще заявити, що мені потрібно
Mircea Ispas

Відповіді:


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

Як бачите, він може працювати в загальному контексті.



1
Звичайно! Він навіть працює з простими структурами або, якщо дано правильний інтерфейс, і класи.
Гійом Ракікот

13

Це не шаблон, і він не працює з локалями, але якщо це не шоу-пробка, то C ++ 17 вже має те, що ви хочете: std::from_chars

Існують перевантаження для всіх типів цілих чисел з плаваючою комою, і інтерфейс однаковий, за винятком останніх параметрів, які відрізняються відповідно для цілих чисел і типів з плаваючою комою (але якщо за замовчуванням нормально, то вам не потрібно що-небудь змінити). Оскільки це не відома локальна функція, вона також досить швидка. Він буде бити будь-який інший рядок для функції перетворення значення і, як правило, це на порядки величини.

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


1
@NathanOliver: stoiі його друзі (конверсії, згадані у запитанні) також не працюють з локалями, так що це не стоп-шоу.
Піт Бекер

9

Ви б не набрали багато, тому що в виразі, як

int x = sto("1");

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

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

що певною мірою перемагає мету забезпечення родової функції. З іншого боку, a

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);, тобто його легко використовувати неправильно.


Я думаю, що покладатися на неявну конверсію тут є хорошою ідеєю. Спроба відключити авто - це проблема. Як правило, я бачив, що це робиться шляхом введення приватного посилання на const у клас, який ініціалізується лише допустимими методами. Я не можу зрозуміти, як можна було б скористатися цим, хоча, оскільки нам належить побудувати цілий об'єкт перетворювача, перш ніж продовжувати. Хммм ....
bremen_matt

Я бачу значення, незважаючи на параметр типу, що не виводиться - як зазначено в запитанні, мотивація полягає в тому, щоб мати можливість використовувати з коду шаблону, де ви перетворюєтесь на тип, який змінюється між інстанціями.
Toby Speight

У чому полягає основна проблема auto x = sto(s)? Ця конкретна реалізація порушена, тому що converter::xце посилання, яке виходить за рамки, але це можна виправити Просто видаліть посилання і покладайтесь на std::stringсемантику руху.
MSalters

@MSalters так, я вважав, що це посилання, яке я вважав проблематичним, але ви маєте рацію, не потрібно використовувати посилання. Що насправді більше мене турбує, це те, що це, здається, є функцією, але фактична функціональність є converter, також я не впевнений, чи найкращим вибором був оператор конвертації шаблонів, що можна виправити. Можливо, це не так погано, як я спочатку думав
idclev 463035818

Я не думаю, що тут є якісь проблеми з посиланням на const. Я розумію, що посилання на const збереже час експлуатації струни, поки не буде знищений перетворювач ( biljeutter.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 ++ потоковий потік - ваш друг. Якщо я правильно розумію, то наступне може бути цікавим для вас. Це 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;
}

Цей метод працює в С ++ 11 і є досить загальним. На мій досвід, цей метод є надійним, але не найбільш ефективним.


Так, це те, що я використав, але продуктивність нижче названих функцій, які часом не бажані
Mircea Ispas
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.