콘텐츠로 이동

C++20 std::format — 타입 안전 문자열 포매팅

std::format은 C++20에서 도입된 타입 안전 문자열 포매팅 라이브러리입니다. Python의 str.format()에서 영감을 받았으며, printf의 간결함과 iostream의 타입 안전성을 결합합니다.


#include <format>
#include <string>
#include <iostream>
int main() {
// 기본 포매팅
std::string s = std::format("Hello, {}!", "world");
std::cout << s << '\n'; // Hello, world!
// 인덱스 기반
std::string s2 = std::format("{0} + {1} = {2}", 1, 2, 3);
std::cout << s2 << '\n'; // 1 + 2 = 3
}

#include <format>
#include <numbers>
// 정수
std::format("{:d}", 42); // "42"
std::format("{:08d}", 42); // "00000042"
std::format("{:#x}", 255); // "0xff"
std::format("{:#b}", 10); // "0b1010"
// 부동소수점
std::format("{:.3f}", 3.14159); // "3.142"
std::format("{:e}", 1234.5); // "1.234500e+03"
std::format("{:g}", 0.0001); // "0.0001"
// 문자열 정렬
std::format("{:>10}", "right"); // " right"
std::format("{:<10}", "left"); // "left "
std::format("{:^10}", "center"); // " center "
std::format("{:*^10}", "hi"); // "****hi****"

#include <print>
std::print("값: {}\n", 42); // 출력 후 개행 없음
std::println("값: {}", 42); // 출력 후 자동 개행
std::println(stderr, "오류: {}", "실패"); // stderr에 출력

#include <format>
struct Color { uint8_t r, g, b; };
template<>
struct std::formatter<Color> {
// 포맷 지정자 파싱 (없으면 기본)
constexpr auto parse(std::format_parse_context& ctx) {
return ctx.begin();
}
// 실제 포매팅
auto format(const Color& c, std::format_context& ctx) const {
return std::format_to(ctx.out(), "#{:02X}{:02X}{:02X}", c.r, c.g, c.b);
}
};
int main() {
Color red{255, 0, 0};
std::string s = std::format("색상: {}", red); // "색상: #FF0000"
}

5. 동적 포맷 문자열 — std::vformat

섹션 제목: “5. 동적 포맷 문자열 — std::vformat”
#include <format>
void log(std::string_view fmt, std::format_args args) {
std::string msg = std::vformat(fmt, args);
// 로그 처리...
}
// 호출
log("{}: {}", std::make_format_args("level", 42));

방식타입 안전성능가독성
printf빠름중간
stringstream느림낮음
std::format빠름높음
{fmt} 라이브러리매우 빠름높음

std::format{fmt} 라이브러리를 표준화한 것으로, 컴파일러 최적화를 통해 printf에 근접한 성능을 냅니다.


#include <format>
// 런타임에 너비와 정밀도 지정
int width = 10;
int precision = 3;
double value = 3.14159;
std::string s = std::format("{:{}.{}f}", value, width, precision);
// " 3.142" (너비 10, 소수점 3자리)
// 런타임 채움 문자는 불가 — 너비/정밀도만 동적 지정 가능
std::string aligned = std::format("{:>{}}", "hi", width); // " hi"

#include <format>
#include <iterator>
#include <string>
// 기존 string에 이어 쓰기
std::string output;
output.reserve(256);
std::format_to(std::back_inserter(output), "Hello, {}!\n", "World");
std::format_to(std::back_inserter(output), "Value: {:.2f}\n", 3.14159);
// 고정 버퍼에 쓰기 (오버플로 방지)
char buf[64];
auto [ptr, ec] = std::format_to_n(buf, sizeof(buf) - 1, "x={}, y={}", 10, 20);
*ptr = '\0';
// buf == "x=10, y=20"

#include <format>
#include <string>
#include <chrono>
enum class LogLevel { Debug, Info, Warning, Error };
std::string_view level_str(LogLevel lvl) {
switch (lvl) {
case LogLevel::Debug: return "DEBUG";
case LogLevel::Info: return "INFO ";
case LogLevel::Warning: return "WARN ";
case LogLevel::Error: return "ERROR";
}
return "?????";
}
template<typename... Args>
void log(LogLevel lvl, std::format_string<Args...> fmt, Args&&... args) {
auto now = std::chrono::system_clock::now();
auto msg = std::format(fmt, std::forward<Args>(args)...);
// 헤더와 메시지를 한 번에 조합
std::string line = std::format("[{}] {}\n", level_str(lvl), msg);
// write to output...
}
// 사용
log(LogLevel::Info, "연결 성공: {}:{}", "127.0.0.1", 8080);
log(LogLevel::Error, "파일 없음: {}", "/etc/config.txt");

std::format은 C++ 문자열 처리의 패러다임을 바꿉니다. 포맷 지정자가 컴파일 타임에 검증되고, 커스텀 타입도 std::formatter 특수화로 자연스럽게 지원됩니다. C++23의 std::print와 함께 사용하면 완전히 printf를 대체할 수 있습니다.