С++ преобразует простые значения в строку
В настоящий момент я использую следующий фрагмент кода, чтобы фиктивно преобразовать базовые типы (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[]
.
Есть ли простой способ предложить элегантное решение этой проблемы?
Ответы
Ответ 1
Насколько я знаю, единственный способ сделать это - специализировать шаблон по типу параметра с помощью 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");
}
Важное примечание: массивы char, переданные этой функции, должны быть пустыми!
Как отмечено в комментариях 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};
}
Ответ 2
Есть ли простой способ предложить элегантное решение этой проблемы?
Так как никто не предложил его, рассмотрите возможность использования boost:: lexical_cast.
Это легко интегрируется со всем, что реализует std:: ostream < оператора и может быть расширен для пользовательских типов.
Ответ 3
Я бы рекомендовал использовать 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
.
Edit:
Dyp предложил, используя 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
, я бы настоятельно рекомендовал прочитать некоторые ответы, потому что они феноменальны: Метапрограмма: отказ определения функции Определяет отдельную функцию
Ответ 4
Простейшим решением является перегрузка для типов, которые вы хотите:
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
.
Ответ 5
Хотя вопрос не в том, что он имеет вид кода, так как у меня уже есть решение, я подумал о его совместном использовании:
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;
}
Ответ 6
Я считаю, самое элегантное решение:
#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
.
Конечно, в С++ 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
, чтобы воспользоваться преимуществами С++ 11 переместить семантику.
Вот почему мое решение лучше, чем избранное в настоящее время решение by @cerkiewny:
-
Он имеет гораздо более широкую применимость, потому что благодаря ADL, это также
определенный для любого типа, для которого преобразование с использованием функции to_string
(не только std::
его версия), см. пример использования ниже.
В то время как решение @cerkiewny работает только для фундаментального
типы и типы, из которых std::string является конструктивным.
Конечно, в его случае можно добавить дополнительные перегрузки
stringify
для других типов, но это гораздо менее твердое решение, если
по сравнению с добавлением новых версий ADL to_string
. И есть шансы
высота, что ADL-совместимая to_string
уже определена в сторонней библиотеке для
тип, который мы хотим использовать. В этом случае с моим кодом вам вообще не нужно писать какой-либо дополнительный код, чтобы сделать работу stringify
.
-
Это более эффективно,
потому что он использует С++ 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
}
И это наиболее вероятно, есть другие проблемы, связанные с этим подходом.