1. はじめに
どこから拾ってきたのか忘れてしまいましたが、それを元に std::string に対して printf みたいな書式付き出力をするクラス(?) を作りました。format は C++20 から使えるようですが、こちらは C++11 くらいから使用でき、既存の使い方に慣れている方ならすぐ使えます。
2. ということでまずはWindows用コード
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 が使える環境であればという条件付きで同様に可能です(一部抜粋)。
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 が使えそうです
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.終わりに
コメントいただいた皆様、ありがとうございました。