std::string に printf のような書式付き出力をしたい

# 1. はじめに
どこから拾ってきたのか忘れてしまいましたが、それを元に std::string に対して printf みたいな書式付き出力をするクラス(?) を作りました。format　は　C++20　から使えるようですが、こちらは　C++11 くらいから使用でき、既存の使い方に慣れている方ならすぐ使えます。

# 2. ということでまずはWindows用コード

``` C++:StringUtil.h

// StringUtil.h
#pragma once

#include    <stdexcept>
#include	<stdarg.h>
#include	<vector>
#include	<string>

class CStringUtil
{
public:
    static std::string format(const char *format, ...)
    {
        va_list marker;

        va_start(marker, format);
        size_t nSize = getFormatSize(format, marker);
        va_end(marker);

        if (nSize <= 0)
        {
            throw std::runtime_error("Failed to calculate format size.");
        }

        //  終端文字分を確保
        std::vector<char> vStr(nSize + 1, '\0'); 

        va_start(marker, format);
        int result = vsnprintf(vStr.data(), vStr.size(), format, marker);
        va_end(marker);

        if (result < 0)
        {
            throw std::runtime_error("String formatting failed.");
        }

        return std::string(vStr.data());
    }

    static std::wstring format(const wchar_t *format, ...)
    {
        va_list marker;

        va_start(marker, format);
        size_t nSize = getFormatSize(format, marker);
        va_end(marker);

        if (nSize <= 0)
        {
            throw std::runtime_error("Failed to calculate format size.");
        }

        //  終端文字分を確保
        std::vector<wchar_t> vStr(nSize + 1, L'\0');

        va_start(marker, format);
        int result = vswprintf(vStr.data(), vStr.size(), format, marker);
        va_end(marker);

        if (result < 0)
        {
            throw std::runtime_error("String formatting failed.");
        }

        return std::wstring(vStr.data());
    }

private:
    static int getFormatSize(const char *format, va_list args)
    {
        return _vscprintf(format, args);
    }

    static int getFormatSize(const wchar_t *format, va_list args)
    {
        return _vscwprintf(format, args);
    }

    CStringUtil() {}
    ~CStringUtil() {}
};

```
# 3. Windows以外はどうする?

書き込む予定の文字数を得る関数ですが、悩ましい問題があります。

std::string 版では vsnprintf(nullptr, 0, format, args);
が使えそうですが、wchar_t版ではいい関数が見当たりません。

Linuxでは UTF-8 が基本なので要らないっちゃぁ要らないのですが、私がたまたま UTF-16LE を扱っていたので欲しかったのです。仕方ないので /dev/null が使える環境であればという条件付きで同様に可能です(一部抜粋)。

```C++
    static int getFormatSize(const char *format, va_list args)
    {
        return vsnprintf(nullptr, 0, format, args);
    }

    static int getFormatSize(const wchar_t *format, va_list args)
    {
    	FILE* fp = fopen("/dev/null", "wb");
    	if(fp == nullptr) {
    		return -1;
    	}
      int size = fwprintf(fp, format, args);
      fclose(fp);
      return size;
    }
```

# 4.別解
コメントいただきました。

POSIX の open_memstream が使えそうです

```c++: string_utility.h

#ifndef HEADER_e5dd4fd2be8a8efa27c4e7ce903e5eb1
#define HEADER_e5dd4fd2be8a8efa27c4e7ce903e5eb1

#include <cstdio>
#include <cwchar>
#include <stdexcept>
#include <string>
#include <type_traits>

namespace string_utility {
namespace detail {
template <class... T>
int fgprintf(FILE *stream, const char *format, T... args) {
    return std::fprintf(stream, format, args...);
}

template <class... T>
int fgprintf(FILE *stream, const wchar_t *format, T... args) {
    return std::fwprintf(stream, format, args...);
}

inline FILE *open_gmemstream(char **bufp, std::size_t *sizep) {
    return open_memstream(bufp, sizep);
}

inline FILE *open_gmemstream(wchar_t **bufp, std::size_t *sizep) {
    return open_wmemstream(bufp, sizep);
}
}  // namespace detail

template <class T, class... U>
typename std::enable_if<std::is_same<T, char>::value || std::is_same<T, wchar_t>::value, std::basic_string<T>>::type
format(const T *format, U... args) {
    T *buf;
    std::size_t len;
    std::FILE *stream = detail::open_gmemstream(&buf, &len);
    if (stream == nullptr) throw std::runtime_error("Failed to open memory stream.");
    int formatted_size = detail::fgprintf(stream, format, args...);
    std::fclose(stream);
    std::basic_string<T> formatted_string(buf, &buf[formatted_size]);
    std::free(buf);

    return formatted_string;
}
}  // namespace string_utility

#endif
```

# 5.終わりに
コメントいただいた皆様、ありがとうございました。