skip to content
Mehdi Mehdikhani
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: stringstream
std::ostringstream oss;
oss << 42;
std::string str1 = oss.str();
// Method 2: sprintf
char 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 simple
std::string str4 = std::to_string(42);

When I Actually Use std::to_string

Most of the time, I use std::to_string for:

  1. 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);
  1. 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";
}
  1. 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") + "}";
}
};
  1. 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 types
std::to_string(42); // int
std::to_string(42u); // unsigned int
std::to_string(42L); // long
std::to_string(42UL); // unsigned long
std::to_string(42LL); // long long
std::to_string(42ULL); // unsigned long long
// Floating point types
std::to_string(3.14f); // float
std::to_string(3.14); // double
std::to_string(3.14L); // long double
// But NOT these:
// std::to_string('A'); // char - doesn't work
// std::to_string(true); // bool - doesn't work

For 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();
}
};
// Usage
ConfigWriter 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 cases
std::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.