C++ конвертировать простые значения в строку
Прямо сейчас я использую следующий фрагмент кода для случайного преобразования основных типов (int
, long
, char[]
такого рода вещи), чтобы std::string
для дальнейшей обработки:
template<class T>
constexpr std::string stringify(const T& t)
{
std::stringstream ss;
ss << t;
return ss.str();
}
Однако мне не нравится тот факт, что это зависит от std::stringstream
, Я пытался с помощью std::to_string
(из репертуара С ++11), однако, он задыхается char[]
переменные.
Есть ли простой способ предложить элегантное решение этой проблемы?
6 ответов
Насколько я знаю, единственный способ сделать это - специализировать шаблон по типу параметра с помощью SFINAE.
Вы должны включить type_traits.
Поэтому вместо вашего кода используйте что-то вроде этого:
template<class T>
typename std::enable_if<std::is_fundamental<T>::value, std::string>::type stringify(const T& t)
{
return std::to_string(t);
}
template<class T>
typename std::enable_if<!std::is_fundamental<T>::value, std::string>::type stringify(const T& t)
{
return std::string(t);
}
этот тест работает для меня:
int main()
{
std::cout << stringify(3.0f);
std::cout << stringify("Asdf");
}
Важное примечание: массивы символов, передаваемые этой функции, должны быть завершены нулем!
Как отмечено в комментариях yakk, вы можете избавиться от нулевого завершения с помощью:
template<size_t N> std::string stringify( char(const& s)[N] ) {
if (N && !s[N-1]) return {s, s+N-1};
else return {s, s+N};
}
Есть ли простой способ предложить элегантное решение этой проблемы?
Так как никто не предложил это, рассмотрите использование boost:: lexical_cast.
Это легко интегрируется со всем, что реализует оператор std::ostream<< и может быть расширено для пользовательских типов.
Я бы порекомендовал использовать enable_if_t
и если вы собираетесь принимать какие-либо символьные переменные, вы специализируете их:
template<typename T>
enable_if_t<is_arithmetic<T>::value, string> stringify(T t){
return to_string(t);
}
template<typename T>
enable_if_t<!is_arithmetic<T>::value, string> stringify(T t){
return static_cast<ostringstream&>(ostringstream() << t).str();
}
template<>
string stringify<char>(char t){
return string(1, t);
}
Здесь я просто специализируюсь char
, Если вам нужно специализироваться wchar
, char16
, или же char32
Вы должны будете сделать это также.
В любом случае для неарифметических типов эти перегрузки будут по умолчанию использовать ostringstream
что является хорошей причиной, если вы перегрузили оператор извлечения для одного из ваших классов, это сработает.
Для арифметических типов это будет использовать to_string
, за исключением char
и все остальное, что вы перегружаете, и те могут непосредственно создать string
,
Редактировать:
Дип предложил использовать ли to_string
принимает аргумент T::type
Как мой enable_if_t
состояние.
Самое простое решение доступно вам, только если у вас есть доступ к is_detected
в #include <experimental/type_traits>
, Если вы просто определите:
template<typename T>
using to_string_t = decltype(to_string(declval<T>()));
Тогда вы можете настроить свой код как:
template<typename T>
decltype(to_string(T{})) stringify(T t){
return to_string(t);
}
template<typename T>
enable_if_t<!experimental::is_detected<to_string_t, T>::value, string> (T t){
return static_cast<ostringstream&>(ostringstream() << t).str();
}
template<>
string stringify<char>(char t){
return string(1, t);
}
Я задал этот вопрос, чтобы выяснить, как использовать to_string
как мое состояние Если у вас нет доступа к is_detected
Я очень рекомендую прочитать некоторые ответы, потому что они феноменальны: Метапрограммирование: ошибка определения функции определяет отдельную функцию
Я считаю, что самое элегантное решение - это:
#include <string>
template <typename T>
typename std::enable_if<std::is_constructible<std::string, T>::value, std::string>::type
stringify(T&& value) {
return std::string(std::forward<T>(value)); // take advantage of perfect forwarding
}
template <typename T>
typename std::enable_if<!std::is_constructible<std::string, T>::value, std::string>::type
stringify(T&& value) {
using std::to_string; // take advantage of ADL (argument-dependent lookup)
return to_string(std::forward<T>(value)); // take advantage of perfect forwarding
}
Здесь, если мы можем построить std::string
с помощью T
(мы проверяем это с помощью std::is_constructible<std::string, T>
), тогда мы делаем это, в противном случае мы используем to_string
,
Конечно, в C++14 вы можете заменить typename std::enable_if<...>::type
с гораздо короче std::enable_if_t<...>
, Пример приведен в более короткой версии кода, ниже.
Ниже приведена более короткая версия, но она менее эффективна, потому что для этого требуется один дополнительный ход std::string
(но если вместо этого мы сделаем только копию, это будет еще менее эффективно):
#include <string>
std::string stringify(std::string s) { // use implicit conversion to std::string
return std::move(s); // take advantage of move semantics
}
template <typename T>
std::enable_if_t<!std::is_convertible<T, std::string>::value, std::string>
stringify(T&& value) {
using std::to_string; // take advantage of ADL (argument-dependent lookup)
return to_string(std::forward<T>(value)); // take advantage of perfect forwarding
}
Эта версия использует неявное преобразование в std::string
тогда возможно, и использует to_string
иначе. Обратите внимание на использование std::move
чтобы использовать в C++11 семантику перемещения.
Вот почему мое решение лучше, чем наиболее популярное в настоящее время решение @cerkiewny:
Он имеет гораздо более широкое применение, потому что, благодаря ADL, он также определен для любого типа, для преобразования которого используется функция
to_string
определяется (не толькоstd::
версия этого), см. пример использования ниже. Принимая во внимание, что решение @cerkiewny работает только для фундаментальных типов и для типов, из которых строится std::string.Конечно, в его случае можно добавить дополнительные перегрузки
stringify
для других типов, но это гораздо менее надежное решение по сравнению с добавлением новых версий ADLto_string
, И шансы на высоту, что ADL-совместимыйto_string
уже определен в сторонней библиотеке для типа, который мы хотим использовать. В этом случае с моим кодом вам вообще не нужно писать никакого дополнительного кода, чтобыstringify
Работа.Это более эффективно, потому что использует преимущества совершенной пересылки C++11 (с использованием универсальных ссылок (
T&&
) а такжеstd::forward
).
Пример использования:
#include <string>
namespace Geom {
class Point {
public:
Point(int x, int y) : x(x), y(y) {}
// This function is ADL-compatible and not only 'stringify' can benefit from it.
friend std::string to_string(const Point& p) {
return '(' + std::to_string(p.x) + ", " + std::to_string(p.y) + ')';
}
private:
int x;
int y;
};
}
#include <iostream>
#include "stringify.h" // inclusion of the code located at the top of this answer
int main() {
double d = 1.2;
std::cout << stringify(d) << std::endl; // outputs "1.200000"
char s[] = "Hello, World!";
std::cout << stringify(s) << std::endl; // outputs "Hello, World!"
Geom::Point p(1, 2);
std::cout << stringify(p) << std::endl; // outputs "(1, 2)"
}
Альтернативный, но не рекомендуемый подход
Я тоже считал просто перегрузку to_string
:
template <typename T>
typename std::enable_if<std::is_constructible<std::string, T>::value, std::string>::type
to_string(T&& value) {
return std::string(std::forward<T>(value)); // take advantage of perfect forwarding
}
И более короткая версия, использующая неявное преобразование в std::string
:
std::string to_string(std::string s) { // use implicit conversion to std::string
return std::move(s); // take advantage of move semantics
}
Но у них есть серьезные ограничения: нам нужно помнить, чтобы написать to_string
вместо std::to_string
везде, где мы хотим его использовать; также он несовместим с наиболее распространенным шаблоном использования ADL:
int main() {
std::string a = std::to_string("Hello World!"); // error
using std::to_string; // ADL
std::string b = to_string("Hello World!"); // error
}
И, скорее всего, есть другие проблемы, связанные с этим подходом.
Самое простое решение - перегрузка для типов, которые вы хотите:
using std::to_string;
template<size_t Size>
std::string to_string(const char (&arr)[Size])
{
return std::string(arr, Size - 1);
}
поскольку to_string
это не шаблон, вы не можете специализировать его, но, к счастью, это проще.
Код предполагает, что массив завершен нулем, но все еще безопасен, если это не так.
Вы также можете поставить using
линия внутри функций, которые вызывают to_string
если у вас есть сильные чувства о том, где using
принадлежит.
Это также имеет то преимущество, что если вы как-то передаете ей строку, не заканчивающуюся нулем, в качестве одного аргумента не будет UB std::string
конструктор делает.
Хотя вопрос не имеет ничего общего с кодом, поскольку у меня уже есть решение, я решил поделиться им:
template <class... Tail>
inline auto buildString(std::string const &head, Tail const &... tail)
-> std::string;
template <class... Tail>
inline auto buildString(char const *head, Tail const &... tail) -> std::string;
template <class... Tail>
inline auto buildString(char *head, Tail const &... tail) -> std::string;
template <class Head, class... Tail>
inline auto buildString(Head const &head, Tail const &... tail) -> std::string;
inline auto buildString() -> std::string { return {}; }
template <class... Tail>
inline auto buildString(std::string const &head, Tail const &... tail)
-> std::string {
return head + buildString(tail...);
}
template <class... Tail>
inline auto buildString(char const *head, Tail const &... tail) -> std::string {
return std::string{head} + buildString(tail...);
}
template <class... Tail>
inline auto buildString(char *head, Tail const &... tail) -> std::string {
return std::string{head} + buildString(tail...);
}
template <class Head, class... Tail>
inline auto buildString(Head const &head, Tail const &... tail) -> std::string {
return std::to_string(head) + buildString(tail...);
}
Использование:
auto gimmeTheString(std::string const &str) -> void {
cout << str << endl;
}
int main() {
std::string cpp_string{"This c++ string"};
char const c_string[] = "this c string";
gimmeTheString(buildString("I have some strings: ", cpp_string, " and ",
c_string, " and some number ", 24));
return 0;
}