1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-03-27

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.終わりに

コメントいただいた皆様、ありがとうございました。

1
2
15

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?