String Conversion with std::to_string
/ 4 min read
Table of Contents
std::to_string is one of those simple C++11 additions that makes everyday programming much cleaner. No more sprintf or stringstream gymnastics for basic number-to-string conversion.
What std::to_string Actually Does
It converts numeric types to std::string with a simple function call:
#include <string>
int number = 42;double pi = 3.14159;long big_number = 123456789L;
std::string str1 = std::to_string(number); // "42"std::string str2 = std::to_string(pi); // "3.141590"std::string str3 = std::to_string(big_number); // "123456789"Much simpler than the alternatives.
Why This Is Better
Before std::to_string, converting numbers to strings was verbose:
// The old ways - verbose and error-prone#include <sstream>#include <cstdio>
// Method 1: stringstreamstd::ostringstream oss;oss << 42;std::string str1 = oss.str();
// Method 2: sprintfchar buffer[32];sprintf(buffer, "%d", 42);std::string str2 = buffer;
// Method 3: snprintf (safer but still clunky)char buffer2[32];snprintf(buffer2, sizeof(buffer2), "%d", 42);std::string str3 = buffer2;
// With std::to_string - clean and simplestd::string str4 = std::to_string(42);When I Actually Use std::to_string
Most of the time, I use std::to_string for:
- Logging and debugging: Quick conversion for output:
void log_status(int user_id, double balance) { std::string message = "User " + std::to_string(user_id) + " has balance: $" + std::to_string(balance); std::cout << message << std::endl;}
// Much cleaner than:// printf("User %d has balance: $%.2f\n", user_id, balance);- Building file names and paths: Dynamic file naming:
std::string create_backup_filename(int version) { return "backup_v" + std::to_string(version) + ".dat";}
std::string get_log_filename() { auto now = std::time(nullptr); return "app_log_" + std::to_string(now) + ".txt";}- JSON or CSV generation: Building data formats:
class DataExporter {public: std::string to_csv_row(int id, const std::string& name, double value) { return std::to_string(id) + "," + name + "," + std::to_string(value); }
std::string to_json_object(int count, bool active) { return "{\"count\":" + std::to_string(count) + ",\"active\":" + (active ? "true" : "false") + "}"; }};- Error messages: Including numeric context in exceptions:
class NetworkConnection {public: void connect(const std::string& host, int port) { if (port < 1 || port > 65535) { throw std::invalid_argument("Invalid port: " + std::to_string(port) + ". Must be between 1 and 65535."); }
if (!attempt_connection(host, port)) { throw std::runtime_error("Failed to connect to " + host + " on port " + std::to_string(port)); } }};Floating Point Precision
std::to_string uses a default precision that might not be what you want:
double precise = 3.141592653589793;std::string str = std::to_string(precise); // "3.141593" - only 6 digits after decimal
// For custom precision, you still need stringstream or format functions#include <iomanip>std::ostringstream oss;oss << std::fixed << std::setprecision(10) << precise;std::string precise_str = oss.str(); // "3.1415926536"The Types It Supports
std::to_string works with all the basic numeric types:
// Integer typesstd::to_string(42); // intstd::to_string(42u); // unsigned intstd::to_string(42L); // longstd::to_string(42UL); // unsigned longstd::to_string(42LL); // long longstd::to_string(42ULL); // unsigned long long
// Floating point typesstd::to_string(3.14f); // floatstd::to_string(3.14); // doublestd::to_string(3.14L); // long double
// But NOT these:// std::to_string('A'); // char - doesn't work// std::to_string(true); // bool - doesn't workFor types it doesn’t support, you’ll need other methods.
Real World Example
Here’s how I use it in a simple configuration writer:
class ConfigWriter { std::ostringstream config_;
public: template<typename T> void add_setting(const std::string& key, T value) { if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>) { config_ << key << " = " << std::to_string(value) << "\n"; } else if constexpr (std::is_same_v<T, bool>) { config_ << key << " = " << (value ? "true" : "false") << "\n"; } else { config_ << key << " = " << value << "\n"; // Assume it's string-like } }
std::string get_config() const { return config_.str(); }};
// UsageConfigWriter config;config.add_setting("max_connections", 100);config.add_setting("timeout_seconds", 30.5);config.add_setting("enable_logging", true);config.add_setting("server_name", std::string("MyServer"));Performance Considerations
std::to_string is convenient but not the fastest option for high-performance scenarios:
// For performance-critical code, consider alternatives:#include <charconv> // C++17
char buffer[32];auto result = std::to_chars(buffer, buffer + sizeof(buffer), 42);if (result.ec == std::errc{}) { std::string fast_conversion(buffer, result.ptr);}But for most use cases, the convenience of std::to_string outweighs the performance cost.
The Pattern I Follow
I use std::to_string when:
- I need simple numeric-to-string conversion
- Performance isn’t critical
- I don’t need custom formatting
I avoid it when:
- I need specific precision or formatting
- Performance is critical (hot loops, etc.)
- I need to convert many numbers at once
// Good use casesstd::string id_str = std::to_string(user_id);std::string error_msg = "Error code: " + std::to_string(error_code);std::string filename = "data_" + std::to_string(timestamp) + ".log";
// Consider alternatives for these// std::string formatted = std::to_string(3.14159); // Precision issues// for (int i = 0; i < 1000000; ++i) { // Performance issues// strings.push_back(std::to_string(i));// }std::to_string is one of those small improvements that makes code more readable and eliminates a whole class of buffer overflow bugs. It’s not fancy, but it’s useful.