Repository: bitcoin/bitcoin
File: src/util/string.h (C/C++)

Code before:

#include <span.h>

#include <array>
#include <cstdint>
#include <cstring>
#include <locale>
#include <sstream>
#include <string>      // IWYU pragma: export
#include <string_view> // IWYU pragma: export
#include <vector>

namespace util {
void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute);

/** Split a string on any char found in separators, returning a vector.
 *
 * If sep does not occur in sp, a singleton with the entirety of sp is returned.
 *
 * Note that this function does not care about braces, so splitting
 * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
 */
template <typename T = Span<const char>>
std::vector<T> Split(const Span<const char>& sp, std::string_view separators)
{
    std::vector<T> ret;
    auto it = sp.begin();
    auto start = it;
    while (it != sp.end()) {
        if (separators.find(*it) != std::string::npos) {
            ret.emplace_back(start, it);
            start = it + 1;
        }
        ++it;
    }
    ret.emplace_back(start, it);
    return ret;
}

/** Split a string on every instance of sep, returning a vector.
 *
 * If sep does not occur in sp, a singleton with the entirety of sp is returned.
 *
 * Note that this function does not care about braces, so splitting
 * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
 */
template <typename T = Span<const char>>
std::vector<T> Split(const Span<const char>& sp, char sep)
{
    return Split<T>(sp, std::string_view{&sep, 1});
}

Review comment (suggestion):
Note: it seems we are explicitly preventing `*` dynamic width/precision specifiers here, which was previously allowed (and checked) with the linter. That's not necessarily bad, it seems we're not currently using them (quick non-exhaustive search) and it can of course be added back in later, and we're throwing instead of silently ignoring it.

Code after:

#include <span.h>
#include <tinyformat.h>

#include <array>
#include <cstdint>
#include <cstring>
#include <locale>
#include <sstream>
#include <string>      // IWYU pragma: export
#include <string_view> // IWYU pragma: export
#include <vector>

namespace util {
/**
 * @brief A wrapper for a compile-time partially validated format string
 *
 * This struct can be used to enforce partial compile-time validation of format
 * strings, to reduce the likelihood of tinyformat throwing exceptions at
 * run-time. Validation is partial to try and prevent the most common errors
 * while avoiding re-implementing the entire parsing logic.
 *
 * @note Counting of `*` dynamic width and precision fields (such as `%*c`,
 * `%2$*3$d`, `%.*f`) is not implemented to minimize code complexity as long as
 * they are not used in the codebase. Usage of these fields is not counted and
 * can lead to run-time exceptions. Code wanting to use the `*` specifier can
 * side-step this struct and call tinyformat directly.
 */
template <unsigned num_params>
struct ConstevalFormatString {
    const char* const fmt;
    consteval ConstevalFormatString(const char* str) : fmt{str} { Detail_CheckNumFormatSpecifiers(fmt); }
    constexpr static void Detail_CheckNumFormatSpecifiers(std::string_view str)
    {
        unsigned count_normal{0}; // Number of "normal" specifiers, like %s
        unsigned count_pos{0};    // Max number in positional specifier, like %8$s
        for (auto it{str.begin()}; it < str.end();) {
            if (*it != '%') {
                ++it;
                continue;
            }

            if (++it >= str.end()) throw "Format specifier incorrectly terminated by end of string";
            if (*it == '%') {
                // Percent escape: %%
                ++it;
                continue;
            }

            unsigned maybe_num{0};
            while ('0' <= *it && *it <= '9') {