1
0

fix: change the behavior of printf in string op.

- add compiler hint for checking the arguments of printf.
- change the return value of printf. from std::expected to normal value. use C++ exception to indicate error.
	* the error of printf usually caused by programmer. so it can be found when testing program.
	* so i use std::logic_error to indicate this and programmer should fix this before releasing program.
- change the use of encoding convertion. for those cases that convertion must be safe, we unwrap it directly.
This commit is contained in:
2025-09-22 22:14:36 +08:00
parent 45e4031b5c
commit c85830902b
22 changed files with 240 additions and 164 deletions

View File

@ -1,19 +1,21 @@
#include "op.hpp"
#include <type_traits>
#include <algorithm>
#include <stdexcept>
namespace yycc::string::op {
#pragma region Printf VPrintf
/// @brief The concept for all viable char type in printf function family
template<typename TChar>
requires(sizeof(TChar) == sizeof(char))
static FormatResult<std::basic_string<TChar>> generic_printf(const TChar* format, va_list argptr) {
concept PrintfSupportedChar = std::is_same_v<TChar, char> || std::is_same_v<TChar, char8_t>;
template<PrintfSupportedChar TChar>
static std::basic_string<TChar> generic_printf(const TChar* format, va_list argptr) {
// Prepare result
std::basic_string<TChar> rv;
// Check format
if (format == nullptr) return std::unexpected(FormatError::NullFormat);
// Prepare variable arguments
va_list args1;
va_copy(args1, argptr);
@ -21,12 +23,13 @@ namespace yycc::string::op {
va_copy(args2, argptr);
// The return value is desired char count without NULL terminal.
// Minus number means error.
// Negative number means error.
int count = std::vsnprintf(nullptr, 0, reinterpret_cast<const char*>(format), args1);
// Check expected size.
if (count < 0) {
// Invalid length returned by vsnprintf.
return std::unexpected(FormatError::NoDesiredSize);
// This may be caused by invalid format string
throw std::logic_error("fail to determine the size of formatted string");
}
va_end(args1);
@ -39,14 +42,15 @@ namespace yycc::string::op {
// Check written size.
if (write_result < 0 || write_result > count) {
// Invalid write result in vsnprintf.
return std::unexpected(FormatError::BadWrittenSize);
// Idk why this can happen.
throw std::logic_error("the size of written formatted string is not expected");
}
// Return value
return rv;
}
FormatResult<std::u8string> printf(const char8_t* format, ...) {
std::u8string printf(const char8_t* format, ...) {
va_list argptr;
va_start(argptr, format);
auto rv = vprintf(format, argptr);
@ -54,11 +58,11 @@ namespace yycc::string::op {
return rv;
}
FormatResult<std::u8string> vprintf(const char8_t* format, va_list argptr) {
std::u8string vprintf(const char8_t* format, va_list argptr) {
return generic_printf(format, argptr);
}
FormatResult<std::string> printf(const char* format, ...) {
std::string printf(const char* format, ...) {
va_list argptr;
va_start(argptr, format);
auto rv = vprintf(format, argptr);
@ -66,7 +70,7 @@ namespace yycc::string::op {
return rv;
}
FormatResult<std::string> vprintf(const char* format, va_list argptr) {
std::string vprintf(const char* format, va_list argptr) {
return generic_printf(format, argptr);
}

View File

@ -1,50 +1,41 @@
#pragma once
#include "../macro/printf_checker.hpp"
#include <string>
#include <string_view>
#include <cstdarg>
#include <functional>
#include <vector>
#include <expected>
namespace yycc::string::op {
enum class FormatError {
NullFormat, ///< Given format string is nullptr.
NoDesiredSize, ///< Fail to fetch the expected size of result.
BadWrittenSize, ///< The written size is different with expected size.
};
template<typename T>
using FormatResult = std::expected<T, FormatError>;
/**
* @brief Perform an UTF8 string formatting operation.
* @param[in] format The format string.
* @param[in] ... Argument list of format string.
* @return The formatted result, or the fail reason.
* @return The formatted result.
*/
FormatResult<std::u8string> printf(const char8_t* format, ...);
std::u8string printf(YYCC_PRINTF_CHECK_FMTSTR const char8_t* format, ...) YYCC_PRINTF_CHECK_ATTR(1, 2);
/**
* @brief Perform an UTF8 string formatting operation.
* @param[in] format The format string.
* @param[in] argptr Argument list of format string.
* @return The formatted result, or the fail reason.
* @return The formatted result.
*/
FormatResult<std::u8string> vprintf(const char8_t* format, va_list argptr);
std::u8string vprintf(const char8_t* format, va_list argptr);
/**
* @brief Perform an ordinary string formatting operation.
* @param[in] format The format string.
* @param[in] ... Argument list of format string.
* @return The formatted result, or the fail reason.
* @return The formatted result.
*/
FormatResult<std::string> printf(const char* format, ...);
std::string printf(YYCC_PRINTF_CHECK_FMTSTR const char* format, ...) YYCC_PRINTF_CHECK_ATTR(1, 2);
/**
* @brief Perform an ordinary string formatting operation.
* @param[in] format The format string.
* @param[in] argptr Argument list of format string.
* @return The formatted result, or the fail reason.
* @return The formatted result.
*/
FormatResult<std::string> vprintf(const char* format, va_list argptr);
std::string vprintf(const char* format, va_list argptr);
/**
* @brief Modify given string with all occurrences of substring \e old replaced by \e new.