본문 바로가기
  • 개발자 Jueony의 블로그
도서 리뷰

C++11 기준 문자열 format 함수 (std::string/std::wstring)

by jueony 2021. 12. 13.

C++11 문법 기준으로 문자열을 formatting 하는 템플릿 함수를 만들어봤다. 메모 겸 올려본다.

(C++20부터는 std::format이 추가되었으므로 그걸 쓰면 된다.)

기본적인 틀은 아래 글을 참고하여 작성했다.

https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf

참고한 코드 대비해서 좋은 점은 아래와 같다.

1. std::string, std::wstring에 대해 모두 사용가능

2. 참고한 코드 대비 메모리 할당/복사가 적음

format.hpp

#include <string>
#include <stdexcept>
#include <cstdio>

#if defined _MSC_VER && _MSC_VER <= 1800
// Visual studio 2013에서는 C++11은 지원되지만 std::snprintf가 
// 정의되어 있지 않기 때문에 명시적으로 정의해준다.

namespace std
{
    template<class ... Args>
    static int snprintf(char* _Buffer, int _BufferCount, const char* _Fomat, Args&&... args)
    {
        return _snprintf(_Buffer, _BufferCount, _Fomat, std::forward<Args>(args)...);
    }
}
#endif



template<class char_t>
struct _FormatFunction{};

template<>
struct _FormatFunction<char>
{
    template<class ... Args>
    int operator()(Args&&... args)
    {
        return std::snprintf(std::forward<Args>(args)...);
    }
}

template<>
struct _FormatFunction<wchar_t>
{
    template<class ... Args>
    int operator()(Args&&... args)
    {
        return std::swprintf(std::forward<Args>(args)...);
    }
}


// 아래 글을 참고하여 작성함.
// https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf

template<class char_t, typename... Args>
std::basic_string<char_t> format(const std::basic_string<char_t>& fmt, Args&&... args)
{
    if(fmt.size() == 0 || sizeof...(Args) == 0)
        return fmt;

    _FormatFunction<char_t> func{};

    int size_s = func(nullptr, 0, fmt.c_str(), args...);
    if(size_s <= 0)
        throw std::runtime_error("Error during formatting."); 

    // null-terminator 가 포함되지 않은 사이즈
    auto size = static_cast<size_t>(size_s);

    // C++11 이후부터, std::string/std::wstring은 null-terminated 되는 것이 보장 되어있다. 
    // (https://stackoverflow.com/questions/11752705/does-stdstring-have-a-null-terminator)
    // 따라서 ret의 실제 버퍼에는 null-terminator가 포함된다
    std::basic_string<char_t> ret(size, 0);

    // 버퍼 사이즈는 null-terminator 를 포함한 사이즈 (size + 1)를 전달해야 한다.
    func(&ret[0], (size + 1) * sizeof(char_t), fmt.c_str(), args...);
    return ret; 
}

예시

int x = 10, y = -30;

std::string str = format("x: %d, y: %d", x, y); // "x: 10, y: -30"
std::wstring wstr = format(L"x: %d, y: %d", x, y); // L"x: 10, y: -30"