Подтвердить что ты не робот

С++ преобразует простые значения в строку

В настоящий момент я использую следующий фрагмент кода, чтобы фиктивно преобразовать базовые типы (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[].

Есть ли простой способ предложить элегантное решение этой проблемы?

4b9b3361

Ответ 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
}

И это наиболее вероятно, есть другие проблемы, связанные с этим подходом.